什么是链式存储存储结构?
单链表的结构
辨析头结点、头指针等易混淆概念
基本的增删改查操作(不带头结点和带头结点)
单链表与顺序表的对比
线性表的链式存储结构在【顺序表详解】一文中我们介绍了一种“用曲线连接”的线性表,“曲线”是一种形象化的语言,实际上并不会存在所谓“曲线”的这种东西。
所谓“曲线连接”即链式存储,那什么是链式存储呢?
在【顺序表详解】一文中介绍顺序存储结构时举了一个例子:
比如,一群孩子肩并肩地站成一排,占据一定的连续土地。这群孩子的队伍非常整齐划一。
这个例子反映在内存中,即为顺序存储结构。
现在孩子们觉得肩并肩站队太过于约束,于是便散开了,想站在哪里就站在哪里,但是队伍不能中断啊。所以他们便手拉着手来保持队伍不断开。
现在孩子们占据的不是连续的土地了,而是任意的某块土地。
这个例子反映在内存中,就是数据元素任意出现,占据某块内存。
上面两张图放在一块对比,就可以看出问题了:
直线 VS 曲线
整齐划一 VS 杂乱无章
连续内存空间 VS 任意内存空间
顺序存储 VS 链式存储
在【顺序表详解】一文中提到过,线性表的特点之一是元素之间有顺序。顺序存储结构靠连续的内存空间来实现这种“顺序”,但链式存储结构的元素是“任意的”,如何实现“顺序”呢?
“任意”和“顺序”乍一看很像反义词,但并不矛盾。小孩子们为了不让队伍中断便“手拉手”,我们要实现“顺序”,就靠那根“像链条一样的曲线”来把元素链接起来,这样就有了顺序。
这就是链式存储。
在【顺序表详解】一文中提到过“顺序存储结构是使用一段连续的内存单元分别存储线性表中的数据的数据结构”。我们照猫画虎,就可以得到“链式存储结构是使用一组任意的、不连续的内存单元来分别存储线性表中的数据的数据结构”的结论。
那我偏就使用连续的内存单元来存数据,可不可以?当然可以。
下图直观地画出了线性表的元素以链式存储的方式存储在内存中。
为了方便画图,我们将表示顺序的曲线人为地拉直,将任意存储的元素人为地排列整齐,形成下图中的逻辑关系:
这种链式存储结构的线性表,简称为链表。
上图的链表之间用一个单向箭头相连,从一个指向另一个,这样整个链表就有了一个方向,我们称之为单链表。
总结一下特点:
用一组任意的存储单元来存储线性表的数据元素,这组存储单元可以是连续的(1和3),也可以是不连续的;
这组任意的存储单元用“一根链子”串起来,所以虽然在内存上是乱序的,但是在逻辑上却仍然是线性的;
单链表的方向是单向的,只能从前往后,不能从后往前;
单链表的实现思路因为单链表是任意的内存位置,所以数组肯定不能用了。
因为要存储一个值,所以直接用变量就行。
因为单链表在物理内存上是任意存储的,所以表示顺序的箭头(小孩子的手臂)必须要用代码实现出来。
而箭头的含义就是,通过 1 能找到 2。
又因为我们使用变量来存储值,所以,换句话说,我们要通过一个变量找到另一个变量。
变量找变量?怎么做到?
C 语言刚好有这个机制——指针。如果你对指针还不清楚,可以移步到文章【如何掌握 C 语言的一大利器——指针?】。
现在万事俱备,只差结合。
一个变量用来存值,一个指针用来存其直接后继的地址,把变量和指针绑在一块构成一个数据元素,啪的一下,就成了。
由于指针中存了其直接后继的地址,这就相当于用一个有向箭头指向了其直接后继。
先明确几个名词:
用来存数据的变量叫做数据域
用来存“直接后继元素的地址”的 指针 叫做指针域
数据域和指针域构成的数据元素叫做 结点 (node)
总结一下,一个单链表 (SinglyLinkedList) 的结点 (Node) 由以下几部分组成:
用来存数据的数据域——data
用来存直接后继结点的的地址的指针域——next
结点的具体实现可以使用 C 语言的结构体来实现结点: