当前位置: 首页 > news >正文

2.数据结构-链表

概述

目标

  1. 链表的存储结构和特点
  2. 链表的几种分类及各自的存储结构
  3. 链表和数组的差异
  4. 刷题(反转链表)

概念及存储结构

先来看一下动态数组 ArrayList 存在哪些弊端

  • 插入,删除时间复杂度高
  • 需要一块连续的存储空间,对内存要求比较高,比如要申请1000M 的数组,如果内存没有连续且足够大的存储空间,则会申请失败,即使内存的剩余空间大于 1000M ,仍然会申请失败

链表 (Linked list) 是一种物理存储单元上非连续非顺序的存储结构 ,链表中的每一个元素称之为节点(Node),节点之间用指针(引用) 连接起来,指针的指向顺序代表了节点的逻辑顺序,节点可以在运行时动态生成;每个节点包括两部分:一个是存储数据元素的数据,另一个是存储下一个节点地址的指针
在这里插入图片描述
链表解决了下面两个问题

  1. 链表天生具备动态扩容的特点,不需要像动态数组那样先申请一个更大的空间,能够避免内存空间的大量浪费
  2. 链表不需要一块连续的内存空间,它通过指针将一组零散的内存块串联起来使用,所以如果申请一个1000M大小的链表,只要内存空间大于这个值,就可以申请,不会出现问题
  3. 但链表也会占更多的空间

链表分类

链表根据其节点之间的连接形式可以分为:单链表双向链表循环链表双向循环链表

单链表

单链表就是链表的最基本的结构,链表通过指针将一组零散的内存块串联在一起,如下图所示,将这个记录下一个节点地址的指针叫作后继指针 next,如果链表中的某个节点为pp的一下节点为q,可以表示为: p.next = q
在这里插入图片描述

单向链表中有两个节点是比较特殊的,它们分别是第一个节点和最后一个节点,习惯性地将第一个节点称为头节点,最后一个节点叫尾节点,其中,头节点用来记录链表的基地址,有了它,就可以遍历得到整条链表,而尾节点特殊的地方是:指针不是指向下一个节点,而是指向一个空地址NULL,表示这是单链表上最后一个节点

与数组一样,链表也支持 数据的查找,插入及删除操作
在进行数组的插入,删除操作时,为了保持内存数据的连续性,需要做大量的数据搬移操作,所以时间复杂度为O(n);链表中插入或者删除一个数据时,并不需要为了保持内存的连续性而搬移节点,因为链表的存储空间本来就是不连续的,所以在链表中插入和删除一个数据,是非常快的
如下图所示,针对链表的插入和删除操作,只要考虑相邻节点的指针改变,插入删除的时间复杂度是O(1),查询到指定到数据仍是O(n)
在这里插入图片描述
在这里插入图片描述
有利有弊,链表要想随机访问第k个元素,就没有数组那么高效了,因为链表中的数据并非连续存储的,所以无法像数组那样,根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址,需要根据一个节点一个节点的依次遍历,直到找到相应的节点,所以,查询的时间复杂得是O(n)

双向链表

单向链表只有一个方向,节点只有一个后继指针 next ,而双向链表,它支持两个方向,每个节点有一个后继指针next指向后面的节点,还有一个前驱指针prev指向前面的节点,如下图所示
在这里插入图片描述
由图可知,双向链表对比单向链表,在存储同样多的数据,需要更多的内存存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性,比如:

  1. 可以在O(1)时间内找到给定结点的前驱节点,而对于单向链表需要O(n)

在很多场景下双向链表都比单向链表更加高效,这就是为什么实际的软件开发中,双向链表尽管比较费内存,但还是比较单链表的应用更加广泛的原因,在java语言中,LinkedHashMap就是用到了双向链表这种数据结构
实际上,这里有一个重要的思想是:用空间换时间的设计思想,根据机器内存空间是否充足,来判断是时间换空间,还是空间换时间

循环链表

循环链表 是一种特殊的单链表,实际上,它跟单链表唯一的区别就在尾节点上,单链表的尾节点指针指向NULL,表示这就是最后的节点了,而循环链表的尾节点指向链表的头节点,循环链表结构如下图中所示
在这里插入图片描述
循环链表的优点是从链尾链头比较方便,当要处理的数据具有环型特点时,特别适合用循环链表

双向循环链表

了解了循环链表双向链表,如果把这两种链表整合在一起就是一个双向循环链表
在这里插入图片描述

链表和数组的差异

链表数组对比

数组和链表是两种截然不同的内存组织方式,因为内存存储的区别,它们插入,删除,随机访问操作的时间复杂度正好相反,看下表

时间复杂度数组链表
插入删除O(n)O(1)
随机访问O(1)O(n)

刷题(反转链表)

反转链表

迭代解法

在这里插入图片描述
代码如下

public class Demo {public static void main(String[] args) {ListNode last = new ListNode(4);ListNode node3 = new ListNode(3, last);ListNode node2 = new ListNode(2, node3);ListNode head = new ListNode(1, node2);ListNode cur = head;while (cur != null) {System.out.println(cur.val);cur = cur.next;}System.out.println("--------");ListNode node = reverseList(head);ListNode cur2 = node;while (cur2 != null) {System.out.println(cur2.val);cur2 = cur2.next;}}public static ListNode reverseList(ListNode head) {ListNode pre = null;ListNode cur = head;while (cur != null) {// 获取 head 下一个节点,缓存ListNode tmp = cur.next;// 当前节点指向前一个节点cur.next = pre;// 指向移动pre = cur;cur = tmp;}return pre;}public static class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) {this.val = val;}ListNode(int val, ListNode next) {this.val = val;this.next = next;}@Overridepublic String toString() {return "ListNode{" +"val=" + val +", next=" + next +'}';}}
}

在这里插入图片描述

递归解法

递归解法,理解之后,会觉得很巧妙

  1. 终止条件是当前节点或者一个节点 == null
  2. 在函数内部,改变节点的指向,也就head 的下一个节点指向 head head.next.next = head
    在这里插入图片描述

代码如下

public class Demo {public static void main(String[] args) {ListNode last = new ListNode(4);ListNode node3 = new ListNode(3, last);ListNode node2 = new ListNode(2, node3);ListNode head = new ListNode(1, node2);ListNode cur = head;while (cur != null) {System.out.println(cur.val);cur = cur.next;}System.out.println("--------");ListNode node = reverseList2(head);ListNode cur2 = node;while (cur2 != null) {System.out.println(cur2.val);cur2 = cur2.next;}}public static ListNode reverseList2(ListNode head) {// 递归终止条件是当前为空,或者下一个节点为空if (head == null || head.next == null) {return head;}// 最后一次递归,返回节点数据值为4的节点ListNode cur = reverseList2(head.next);// 此时是在节点数值为3的节点执行方法中// head.next 代表节点4,head 代表节点 3head.next.next = head;// 清除原来 节点3批向节点4的指针,防止节点3,4之间成循环链表head.next = null;return cur;}public static class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) {this.val = val;}ListNode(int val, ListNode next) {this.val = val;this.next = next;}@Overridepublic String toString() {return "ListNode{" +"val=" + val +", next=" + next +'}';}}
}

调试这个递归函数
在这里插入图片描述
由上图可以看出,4节点在执行完函数后,返回至3节点所在执行函数,所以 head.next.next 意思是 3节点的下一个节点(4节点)的指针指向3节点,即完成了3,4节点指向调转,这个地方需要理解

在这里插入图片描述
最终结果如下图
在这里插入图片描述

结束

至此 链表 就分析完了,如有问题,欢迎评论留言

相关文章:

2.数据结构-链表

概述 目标 链表的存储结构和特点链表的几种分类及各自的存储结构链表和数组的差异刷题(反转链表) 概念及存储结构 先来看一下动态数组 ArrayList 存在哪些弊端 插入,删除时间复杂度高需要一块连续的存储空间,对内存要求比较高,比如要申请…...

B站数据质量保障体系建设与实践

本文将分享 B 站数据质量保障体系的建设和实践。文章将关注数仓和建模的相关方法论,讲解 B 站数仓平台团队在数仓建设和建模过程中所做的工作,并分享质量保障方面取得的成果。 一、背景目标 首先,分享一下 B 站数据质量保障的背景和目标。 …...

uniapp开发小程序无法上传图片的解决方法

登录小程序后台,第一步菜单栏 设置 第二步,用户隐私保护 更新 第三步 选2 第四步 勾选需要的接口,并说明 等审核通过后,一会就能正常上传图片。...

shell基础回顾

0.vim命令 vim gg 移动到文档第一行 G 移动到文档最后一行 :set nu 显示行号 :set noun 取消行号 nG 移动到指定n行,例如20G $ 移动到行尾 0 移动到行头 clrtf 屏幕向下移动一页 clrtb 屏幕向上移动一页 :%sword1word2g 搜索文本&#xff…...

[云原生案例1.] 构建LNMP架构并运行Wordpress个人博客平台

文章目录 1. 当前需求2. 前置准备3. 搭建过程3.1 创建自定义网络3.2 部署并配置nginx3.2.1 创建工作目录并上传相关软件包3.2.2 解压缩相关软件包3.2.3 编写Dockerfile文件3.2.4 编写nginx.conf文件3.2.5 创建nginx镜像3.2.6 运行容器 3.3 部署并配置mysql3.3.1 创建工作目录3.…...

C++归并排序算法的应用:计算右侧小于当前元素的个数

题目 给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。 示例 1: 输入:nums [5,2,6,1] 输出:[2,1,1,0] 解释: 5 …...

python类如何实例化对象

python类如何实例化对象 1、把类看作是定制的数据类型。既然是类型,只能用来表示数据的类型,不能直接用来保存数据。**要保存数据,首先需要创建一个类似于这类容器的东西,称为对象(或例子)。通过类别产生对象的过程称为例子。 2、…...

基于GB28181-2022实现web无插件播放H265视频

目前发布的GB28181-2022增加了对前端设备视频H265编码格式的支持,所以实现国标平台通过浏览器对H265视频流的无插件的解码播放将是未来的趋势。 目前大多的方案都是通过平台端把H265转码为H264,再推送到web前端进行解码播放,这种方式因为需要…...

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 第六章 muduo网络库简介

2010年3月作者写了一篇《学之者生,用之者死——ACE历史与简评》(http://blog.csdn.net/Solstice/archive/2010/03/10/5364096.aspx,ACE是(Adaptive Communication Environment)是一个C编写的开源框架,用于开…...

「免费活动」敏捷武林上海站 | 与 Scrum.org CEO 面对面

活动介绍 过去的几年里,外界的风云变幻为我们的生活增添了一些不一样的色彩。在VUCA世界的浪潮里,每一个人都成为自己生活里的冒险家。面对每一次的变化,勇于探索未知,迎接挑战,努力追逐更好的自己。 七月&#xff0…...

深入大模型与ChatGPT

关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、大模型原理 1.Transformer (1)求知之路:LLM 学到了什么知识 LLM 从海量自由文本中学习了大量知识,如果把这些知识做粗略分类的话,…...

ubuntu(18.04)中架设HiGlass docker镜像服务,已尝试mcool、bedpe、wig格式文件

前言 使用到的软件 docker 文档 : https://www.docker.com/ HiGlass 文档:http://docs.higlass.io/higlass_docker.html#running-locally https://github.com/higlass/higlass-dockerhiglass-docker 地址:https://github.com/higla…...

通过API和无代码开发,邻医云如何连接电商平台,集成CRM和客服系统

通过API连接电商平台:邻医云的实践 邻医云,一款致力于改变中国医药行业传统经营方式的技术服务产品,用技术的力量帮助实现数字化转型。邻医云已经在零售、仓储物流、互联网医院、工业等各个领域与各大平台进行合作,帮助客户降低成…...

Python selenium元素的定位

视频版教程:一天掌握python爬虫【基础篇】 涵盖 requests、beautifulsoup、selenium 对象的定位应该是自动化测试的核心,要想操作一个对象,首先应该识别这个对象。一个对象就是一个人一样,他 会有各种的特征(属性&…...

Android图形系统之HWComposer、ComposerHal、ComposerImpl、Composer、Hwc2::Composer实例总结(十四)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…...

MASK-RCNN tensorflow环境搭建

此教程默认你已经安装了Anaconda,且tensorflow 为cpu版本。为什么不用gpu版本,原因下面解释。 此教程默认你已经安装了Anaconda。 因为tensorflow2.1后的gpu版,不支持windows。并且只有高版本的tensorflow才对应我的CUDA12.2; 而…...

企业级开发命名规范有哪些?

企业级开发通常会遵循一些命名规范以提高代码的可读性、可维护性和一致性。以下是一些常见的企业级开发命名规范: 1:变量和函数命名: 使用有意义的名称,能够清晰描述变量或函数的用途和功能。使用驼峰命名法(camelCa…...

sitespeedio.io 前端页面监控安装部署接入influxdb 到grafana

1.docker部署influxdb,部署1.8一下,不然语法有变化后面用不了grafana模板 docker run -d -p 8086:8086 --name influxdb -v $PWD/influxdb-data:/var/lib/influxdb influxdb:1.7.11-alpine docker exec -it influxdb_id bash #influx create user admin with pass…...

ModStartCMS v7.5.0 内外网映射节流,安全使用增强

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用,支持后台一键快速安装,让开发者能快的实现业务功能开发。 系统完全开源,基于 Apache 2.0 开源协议,免费且不限制商业使用。 功能特性 丰富的模块市…...

【LVS实战】02 搭建一个LVS-NAT模式实验

一、网络结构 用虚拟机搭建如下的几台机器,并配置如下的ip 关于虚拟机网卡和网络的配置,可以参考 iptables章节,05节:网络转发实验 主机A模拟外网的机器 B为负载均衡的机器 C和D为 RealServer 二、C和D主机的网关设置 C和D机…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来&#xf…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes&#xff0…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

Java求职者面试指南:计算机基础与源码原理深度解析

Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...

API网关Kong的鉴权与限流:高并发场景下的核心实践

🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...

VisualXML全新升级 | 新增数据库编辑功能

VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...