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节点指向调转,这个地方需要理解

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

结束

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/175521.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NUXT前端服务端渲染技术框架

服务端渲染又称SSR(Server Side Render)实在服务端完成页面的内容,而不是在客户端通过AJAX获取数据 优势:更好的SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面 Nuxt.js是一个基于Vue.js的轻量级应用框架&a…

【WSL 2】Windows10 安装 WSL 2,并配合 Windows Terminal 和 VSCode 使用

【WSL 2】Windows10 安装 WSL 2,并配合 Windows Terminal 和 VSCode 使用 1 安装 Windows Terminal2 安装 WSL 23 在 Windows 文件资源管理器中打开 WSL 项目4 在 VSCode 中使用 WSL 24.1 必要准备4.2 从 VSCode 中 Connect WSL4.3 从 Linux 中打开 VSCode 1 安装 W…

计算机网络第3章-TCP协议(2)

TCP拥塞控制 TCP拥塞控制的三种方式: 慢启动、拥塞避免、快速恢复 慢启动 当一条TCP连接开始时,cwnd的值是一个很小的MSS值,这使得初始发送速率大约为MSS/RTT。 在慢启动状态,cwnd的值以1个MSS开始并且每当传输的报文段首次被…

塔望食观察丨从“一药难求”看国内退烧药品牌是怎样炼成的

随着新冠疫情防疫的全面放开,感染患者不断增多,市民在未知的恐慌中开启了囤药模式,药店中的“四类药”(退烧、止咳、抗病毒、抗生素类药品)被一抢而空,尤其是以退烧类药物更为短缺,以解热镇痛的…

贪心算法总结(未完结)

贪心的定义(摘自百度百科) 贪心算法(greedy algorithm,又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的…

4.2 SSAO算法 屏幕空间环境光遮蔽

一、SSAO介绍 AO 环境光遮蔽,全程Ambient Occlustion,是计算机图形学中的一种着色和渲染技术,模拟光线到达物体能力的粗略的全局方法,描述光线到达物体表面的能力。 SSAO 屏幕空间环境光遮蔽,全程 Screen Space Amb…

掌握组件缓存:解开Vue.js中<keep-alive>的奥秘

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

【C++】搜索二叉树

提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、搜索二叉树概念二、搜索二叉树的操作1.插入2. 查找3. 中序遍历4. 删除 三、默认成员函数1.析构函数2.拷贝构造3. 赋值运算符重载 四、完整代码 一、搜索二叉树概…

「实用技巧」后端如何使用 Eolink Apikit 快速调试接口?

程序员最讨厌的两件事: 写文档 别人不写文档 写文档、维护文档比较麻烦,而且费时,还会经常出现 API 更新了,但文档还是旧的,各种同步不一致的情况,从而耽搁彼此的时间,大多数开发人员不愿意写…

抽奖之星软件,可用作随机分组分班、比赛顺序抽签摇号

抽奖之星软件简介 抽奖之星,极品晚会抽奖软件,大气美观,功能全面,请安装试用或咨询客服。支持作弊内定、指定中奖人、名单分组、中奖几率。(www.wsgsoft.com/plds/) 比赛顺序抽签设置 奖项设置:只一个奖项即可&…

分享一下怎么做一个商城小程序

如何制作一个商城小程序:功能解析、设计思路与实现方法 一、引言 随着移动设备的普及和微信小程序的兴起,越来越多的消费者选择在商城小程序上进行购物。商城小程序具有便捷、高效、即用即走等特点,为企业提供了新的销售渠道和推广方式。本…

Kubernetes Taint(污点) 和 Toleration(容忍)

Author:rab 目录 前言一、Taint(污点)1.1 概述1.2 查看节点 Taint1.3 标记节点 Taint1.4 删除节点 Taint 二、Toleration(容忍) 前言 Kubernetes 中的污点(Taint)和容忍(Toleration…

学习笔记|正态分布|图形法|偏度和峰度|非参数检验法|《小白爱上SPSS》课程:SPSS第三讲 | 正态分布怎么检验?看这篇文章就够了

目录 学习目的软件版本原始文档为什么要假设它服从正态分布呢?t检验一、图形法1、频数分布直方图解读 2、正态Q-Q图操作解读 3、正态P-P图SPSS实战操作解读 二、偏度和峰度解读: 三、非参数检验法注意事项 四、规范表达五、小结划重点 学习目的 SPSS第三讲 | 正态…

Shopee流量和销量不佳?或许你没有掌握正确的引流方法

很多卖家做了很久,但是发现流量和销量都没怎么增长,今天陈哥就分享一下如何正确的引流。 以下是一些有效的引流策略: 1. 站内引流:选择高性价比的潮流商品,根据目标客户群和重点品类进行选品。优化商品名称和描述&am…

Power BI 傻瓜入门 18. 让您的数据熠熠生辉

本章内容包括: 配置Power BI以使数据增量刷新发现使用Power BI Desktop and Services保护数据集的方法在不影响性能和完整性的情况下管理海量数据集 如果有更新的、更相关的数据可用,旧数据对组织没有好处。而且,老实说,如果数据…

设计模式_观察者模式

观察者模式 介绍 设计模式定义案例问题堆积在哪里解决办法观察者是行为型设计模式 多个对象 观察 1个对象小强考试完 成绩公布了 家长/同学得知成绩后 做出不同反应一个一个通知很麻烦 先通知谁 也有讲究的 信息发布方 抽象出一个信息管理类 负责管理监听者 类图 代码 Obse…

学习视频剪辑:如何从指定时段快速抽出视频图片!高效技巧分享

随着数字媒体的普及,越来越多的人开始接触视频剪辑。在视频剪辑过程中,有时候我们需要从指定时段快速抽出视频图片。这不仅可以帮助我们提高剪辑效率,还可以让我们的视频更加丰富多彩。本文将分享一些高效技巧,帮助你轻松实现从指…

尚未解决:use_python()和use_virtualenv()的使用

reticulate包为Python和R之间的互操作性提供了一套全面的工具。该包包含以下功能: 以多种方式从R调用Python,包括RMarkdown、获取Python脚本、导入Python模块以及在R会话中交互使用Python。 R和Python对象之间的转换(例如,R和Pan…

Three.js 开发引擎的特点

Three.js 是一个流行的开源 3D 游戏和图形引擎,用于在 Web 浏览器中创建高质量的三维图形和互动内容。以下是 Three.js 的主要特点和适用场合,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作…

​CRM中的大客户销售是什么?​

对企业来说,大客户可能贡献了大部分的销售业绩。什么样的客户可以被认定为是大客户?大客户销售与普通销售有何区别?针对大客户又该采取什么样的销售策略呢?从回答这几个问题开始,我们来说说CRM中的大客户销售是什么&am…