文章目录
0. 相关文章
1. 初识链表
线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是 n 个具有相同特性的数据元素的有限序列。
线性表根据底层的物理结构可以分为顺序表和链表两种。顺序表底层是一块连续的内存空间(我们常说的数组);而链表底层的内存空间则是非连续、非顺序的,由"链子"(也就是指针)将他们串起来。
如果还不理解,我们可以想象成上学时候排座位。
顺序表就是大家课桌紧紧连在一起,如下图:
链表呢就是大家的课桌没有顺序,但是每个同学手中有一根绳子连着下一个同学。
2. 链表相关概念
节点: 有时又称"结点",在链表中,每一个节点都是由值和指向下一个节点的地址组成的独立的单元,称为一个节点。
头节点: 对于单链表来说,如果知道了第一个元素,就可以通过遍历访问整个链表,因此第一个节点尤为重要,一般称为头节点。
虚拟节点: 在做题一级在工程里进程会看到虚拟节点的概念,其实就是一个节点(dummyNode),它的 next 指针指向 head,也就是 dummyNode.next = head。
3. 链表的分类
从结构上进行区分,链表可以分为:单向链表(Singly Linked)、双向链表(Doubly Linked List)和循环链表(Circular List),在一些资料里还会有块状链表或者多重链表(Multiply Linked List),绝大对数情况下,我们只会用到下面三种链表。
3.1 单向链表(Singly Linked)
单向链表的结构比较简单,节点由"数组+next 指针"组成,每个节点通过它的 next 指针指向下一个节点。遍历的时候只能从前向后遍历,因此称之为单向链表。
我们在前面看到的那个链表,其实就是单向链表,如下图:
3.2 双向链表(Doubly Linked List)
双向链表顾名思义,它不仅有指向下一个节点的指针,也有指向上一个节点的指针。遍历时可以从前向后遍历,也可以从后向前遍历。如下图所示:
3.3 循环链表(Circular List)
循环链表顾名思义,就是链表中最后一个节点指向第一个节点,整个链表形成一个环。因此从表中任一节点触发都可以找到其余所有节点。
4. 链表的使用场景
前面我们讲到过"数组"和"链表"在底层内存上的实现差异,那么都是线性表,什么时候我们该使用数组,什么时候该使用链表呢?
先说一个结论:“数组"适合"改”、"查"较多的场景,“链表"适合"增”、"删"较多的场景。
其实这一点我们可以根据他们的底层差异去理解,我们对线性表的使用无非也是"增"、“删”、“改”、“查"这几个基础操作。
对于数组来说,想要在数组中间实现"增”、"删"操作的代价是极高的
- 如下图所示:小灰、小黑、小红、小蓝、小黄此时分别坐在 0、1、2、3、4 号座位上
- 此时我们想要在小黑后面插入一条数据"小绿",也就是说小绿需要坐在 2 座位上;但此时 2 号作为上已经有小红了,那我们应该怎么办呢?方法就是 2 之后座位上的人都依次向后挪一个位置,将 2 号座位释放出来给小绿坐。
- 由上图可以看出,特别是在数组长度特别长时,向中间插入(删除也是一个道理)一个数据的代价是很大的。而链表却不同,例如我们有以下一个链表,我们想要在 data1 和 date2 之间插入一个 date1.5,此时我们应该怎么操作呢?
- 我们只需要吧 data1 的 next 指针想 date1.5,把 date1.5 的 next 指针指向 date2 即可。
从上可以看出在“增”、“删”较多的场景,链表无疑是更高效的,那么数组的优势是什么呢?基于数组的内存连续性,在执行"查"、“改"操作时,数组可以通过下标索引直接进行存取(arr[2]=3),而链表却需要从某一节点开始遍历,直到找到我们需要操作的节点,然后进行"查”、"改"操作。