5L-链表导论心法
链表是比数组稍微复杂一点的数据结构,也是两个非常重要与基本的数据结构。如果说数组是纪律严明排列整齐的「正规军」那么链表就是灵活多变的「地下党」。
关注公众号 MageByte,有你想要的精彩内容。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。链表的节点由数据和一个或多个指针域组成。如果不考虑插入、删除操作之前查找元素的过程,只考虑纯粹的插入与删除,那么链表在插入和删除操作上的算法复杂 O(1)。
链表大集结
电影中我们看过,一个特务只知道自己上级、下级的信息,地下党通过这种单线联络的的方式灵活隐秘的传递着消息。链表家族也有多种分类,最简单的单链表、循环链表、双向链表、双向循环链表。
底层数据结构与数组最大的区别,数组需要一块连续的内存空间来存储,而链表并不需要一块连续的内存空间,它通过「指针」将一组零散的内存块串联起来使用。这就是地下党灵活多变的本质原因,可随时切换自己的上下级。
单链表
我们把内存块称为链表的「节点」,为了把所有分散的节点串连起来,每个节点除了存储数据外,还需要记录节点的下一个节点的地址,叫做「后继指针 next」。

有两个节点需要注意,分别是第一个节点「头节点」和最后一个节点「尾节点」。头结点记录链表的基地址,这样就可以遍历得到整条链表数据,而尾节点的 「next」只想一个 空地址 NULL,代表这是最后一个节点。
链表的删除与插入操作,只需要考虑相邻节点的指针改变,所以时间复杂度是 O(1)。
有利就有弊,链表想要随机访问第 j 个元素,就没有数组高效。链表的数据并不是连续存储的,无法像数组一样根据首地址和下标通过寻址公式就可以计算出对应的 j 位置内存地址,需要根据指针一个一个节点的一次遍历,直到查找到对应的节点。
这个就像地下党组织,每个人只知道自己的后边是谁,想要把消息传到 k 特务,就需要从消息发布者开始通知他的下一级,直到 k 为止。所以随机访问的性能没有数组好,时间复杂度是 O(n)。
插入节点
尾部插入
与数组类似,插入节点也可以分头部插入、中部插入、尾部插入。尾部插入最简单,把最后一个节点的「next」指针指向新插入的节点即可。
头部插入
分为两个步骤。
第一步,把新节点的「next」指针指向原先的头节点。
第步,把新节点变为链表的头节点。
中间插入
同样分为两个步骤。
- 把插入位置的节点前置节点的「next」指针指向指定插入的新节点。
- 将新节点的「next」指针指向前置节点的「next」指针原先所指定的节点。
删除节点
单链表的删除也分为三种情况。
- 头部删除
- 中部删除
- 尾部删除
尾部删除最简单,把倒数第二个节点的「next」指针指向 null,同时把要删除的节点全都设置 null 让垃圾收集器回收即可。
头部删除,把原先链表的头节点的「next」节点设置成头结点,并且把原来的头结点设置 null 便于 gc 即可。
中间删除,把要删除的节点前置节点的「next」指针指向被删除节点的「next」指针即可。
对于删除与插入,只要我们画下图就很清晰了。
循环链表
循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。从我画的循环链表图中,你应该可以看出来,它像一个环一样首尾相连,所以叫作“循环”链表。

跟单链表差别不大,主要就是尾部节点遍历到头部节点方便。
双向链表
实际开发中最常见的链表-双向链表。Java 中的 LinkedList就是一个双向链表。
单链表只有一个方向,每个节点只有一个后继指针 「next」指向下一个节点。双向链表则是有两个指针,每个节点分别有一个「next」指针指向后面节点和一个「prev」指针指向前置节点。

可以看出来,双向链表需要额外的两个空间存储后继节点和前驱节点地址。所以存储同样多的数据,双向链表比单项链表占用更多的空间,但是优势则是可以双向遍历。
双向链表可以支持在 O(1) 时间复杂度情况定位到前驱结点,正是这样的特点,也使双向链表在某些情况下的插入、删除等操作都要比单链表简单、高效。
之前我们说单项链表的删除、插入时间复杂度是 O(1)了,那为啥这里还说双向链表的删除、插入还能更高效呢?
从链表删除一个元素,其实有两种情况:
- 删除「值等于给定的内容」的节点。
- 删除给定指针指向的节点。
第一种情况,其实都一样,不管是单项还是双向都需要从头节点遍历比对找到要删除的节点。
对于第二种情况,我们已经找到了要删除的结点,但是删除某个结点 q 需要知道其前驱结点,而单链表并不支持直接获取前驱结点,所以,为了找到前驱结点,我们还是要从头结点开始遍历链表,直到 p->next=q,说明 p 是 q 的前驱结点。
但是对于双向链表来说,这种情况就比较有优势了。因为双向链表中的结点已经保存了前驱结点的指针,不需要像单链表那样遍历。所以,针对第二种情况,单链表删除操作需要 O(n) 的时间复杂度,而双向链表只需要在 O(1) 的时间复杂度内就搞定了!
双向循环链表

数组与链表性能比较
数组和链表都属于线性的数据结构,用哪一个好呢?其实数据结构没有绝对的好坏,各有千秋。
| 查找 | 更新 | 删除 | 插入 | |
|---|---|---|---|---|
| 数组 | O(1) | O(1) | O(n) | O(n) |
| 链表 | O(n) | O(1) | O(1) | O(1) |
数组简单易用,在实现上使用的是连续的内存空间,可以借助 CPU 的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,所以对 CPU 缓存不友好,没办法有效预读。
对链表进行频繁的插入、删除操作,还会导致频繁的内存申请和释放,容易造成内存碎片,如果是 Java 语言,就有可能会导致频繁的 GC(Garbage Collection,垃圾回收)。
本文讲解了链表的理论原理与知识,下一篇将代码实操撸一遍。
课后作业
如何基于链表实现一个 LRU 缓存淘汰算法?公众号后台回复 LRU,可以获取答案。
欢迎加群与我们讨论分享,我们第一时间反馈。

推荐阅读
4.线性表之数组
5L-链表导论心法的更多相关文章
- 6L-单向链表实现
关注公众号 MageByte,有你想要的精彩内容.文中涉及的代码可访问 GitHub:https://github.com/UniqueDong/algorithms.git 上一篇<链表导论心 ...
- 7L-双线链表实现
链表是基本的数据结构,尤其双向链表在应用中最为常见,LinkedList 就实现了双向链表.今天我们一起手写一个双向链表. 文中涉及的代码可访问 GitHub:https://github.com/U ...
- 《算法导论》习题解答 Chapter 22.1-2(邻接矩阵与链表)
链表如图: 矩阵: 1 2 3 4 5 6 7 1 0 1 1 0 0 0 0 2 1 0 0 1 1 0 0 3 1 0 0 0 0 1 1 4 0 1 0 0 0 0 0 5 0 1 0 0 0 ...
- 基于visual Studio2013解决算法导论之022队列实现(基于链表)
题目 基于链表的队列实现 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <time.h> ...
- 基于visual Studio2013解决算法导论之020单链表
题目 单链表操作 解决代码及点评 #include <iostream> using namespace std; struct LinkNode { public: LinkNo ...
- 基于visual Studio2013解决算法导论之018栈实现(基于链表)
题目 用链表实现栈 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <time.h> #in ...
- 【原创】《算法导论》链表一章带星习题试解——附C语言实现
原题: 双向链表中,需要三个基本数据,一个携带具体数据,一个携带指向上一环节的prev指针,一个携带指向下一环节的next指针.请改写双向链表,仅用一个指针np实现双向链表的功能.定义np为next ...
- "《算法导论》之‘线性表’":基于数组实现的单链表
对于单链表,我们大多时候会用指针来实现(可参考基于指针实现的单链表).现在我们就来看看怎么用数组来实现单链表. 1. 定义单链表中结点的数据结构 typedef int ElementType; cl ...
- "《算法导论》之‘线性表’":基于指针实现的单链表
对于单链表的介绍部分参考自博文数组.单链表和双链表介绍 以及 双向链表的C/C++/Java实现. 1. 单链表介绍 单向链表(单链表)是链表的一种,它由节点组成,每个节点都包含下一个节点的指针. ...
随机推荐
- Object.defineProperty注意事项
Object.defineProperty() 方法设置属性时,属性不能同时声明访问器属性( set 和 get )和 writable 或者 value 属性. 意思就是,某个属性设置了 writa ...
- Class file version does not support constant tag 16 in class file
启动服务时提示 Caused by: java.lang.ClassFormatError: Class file version does not support constant tag 16 i ...
- [讨论] 平台建设,我们从架构中去掉kafka?
目 录 1. 概述... 2 2. 原有结构(带kafka)... 2 3. 改造后的结构(去掉kafka)... 3 4. 对比... 4 1. ...
- git上传命令步骤
1.登陆github后,进入Github首页,点击New repository新建一个项目 2. 填写相应信息后点击create repository即可 Repository name: 仓库名称( ...
- 7-7 jmu_python_是否是数 (10 分)
本题要求从键盘输入一个字符串,判断该串是否属于整数.浮点数或者复数的表示 输入格式: 输入一个字符串 输出格式: 输出yes或no 输入样例: -299 输出样例: yes a = input() t ...
- .Net Core 使用jenkins 部署到远程服务器docker
最近一直在研究docker和kubernetes,小型的项目其实也用不到k8s,本文就是描述如何用jenkins 进行构建asp.net core 3.1项目并且发布到远程服务器docker容器中.本 ...
- vue中的插槽(slot)
vue中的插槽,指的是子组件中提供给父组件使用的一个占位符,用<slot></slot>标签表示,父组件可以在这个占位符中填充任何模板代码,比如HTML.组件等,填充的内容会替 ...
- Java8 内置的函数式接口
1.Java8 内置的四大核心函数式接口 (1)Consumer<T> : 消费型接口 void accept(T t); (2)Supplier<T> : 供 ...
- ElementUI el-table 在flex下的宽度自适应问题
BUG:在flex容器下面的一个flex:1的子容器里面写了个el-table用来展示列表数据,在做宽度自适应测试的时候发现该组件的宽度只会增加不会缩小. Debug:通过控制台发现组件生成的tabl ...
- linux下 GUI 数码相册项目 持续更新中
GITHUB: https://github.com/nejidev/digital_photo_album 本项目,是部分参考别人的项目,是全新从0编写的.算法实现和别人肯定是不同的,github ...