1010
1111![ ] ( https://qcdn.itcharge.cn/images/20211208180037.png )
1212
13- 如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 ` next ` 」。
13+ 如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 $ next$ 」。
1414
1515在链表中,数据元素之间的逻辑关系是通过指针来间接反映的。逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。
1616
2020
2121- ** 缺点** :不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。
2222
23- 接下来先来介绍一下除了单链表之外 ,链表的其他几种类型。
23+ 接下来我们来介绍一下除了单链表之外 ,链表的其他几种类型。
2424
2525### 1.2 双向链表
2626
2727> ** 双向链表(Doubly Linked List)** :链表的一种,也叫做双链表。它的每个链节点中有两个指针,分别指向直接后继和直接前驱。
2828
29- 从双链表的任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。
29+ - ** 双向链表特点 ** : 从双链表的任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。
3030
3131![ ] ( https://qcdn.itcharge.cn/images/20211208103220.png )
3232
3333### 1.3 循环链表
3434
3535> ** 循环链表(Circular linked list)** :链表的一种。它的最后一个链节点指向头节点,形成一个环。
3636
37- 从循环链表的任何一个节点出发都能找到任何其他节点。
37+ - ** 循环链表特点 ** : 从循环链表的任何一个节点出发都能找到任何其他节点。
3838
3939![ ] ( https://qcdn.itcharge.cn/images/20211208180048.png )
4040
41- 接下来我们以单链表为例 ,介绍一下链表的基本操作。
41+ 接下来我们以最基本的「单链表」为例 ,介绍一下链表的基本操作。
4242
4343## 2. 链表的基本操作
4444
4545数据结构的操作一般涉及到增、删、改、查 4 种情况,链表的操作也基本上是这 4 种情况。我们一起来看一下链表的基本操作。
4646
4747### 2.1 链表的结构定义
4848
49- 链表是由链节点通过 ` next ` 链接而构成的,所以先来定义一个简单的链节点类,即 ` ListNode ` 类。 ` ListNode ` 类使用成员变量 ` val ` 表示数据元素的值,使用指针变量 ` next ` 表示后继指针 。
49+ 链表是由链节点通过 $ next$ 链接而构成的,我们可以先定义一个简单的「链节点类」,再来定义完整的「链表类」 。
5050
51- 然后再定义链表类,即 ` LinkedList ` 类。 ` LinkedList ` 类中只有一个链节点变量 ` head ` 用来表示链表的头节点 。
51+ - ** 链节点类(即 ListNode 类) ** :使用成员变量 $val$ 表示数据元素的值,使用指针变量 $next$ 表示后继指针 。
5252
53- 我们在创建空链表时,只需要把相应的链表头节点变量设置为空链接即可。在 ` Python ` 里可以将其设置为 ` None ` ,其他语言也有类似的惯用值,比如 ` NULL ` 、` nil ` 、` 0 ` 等。
53+ - ** 链表类(即 LinkedList 类)** :使用一个链节点变量 $head$ 来表示链表的头节点。
54+
55+ 我们在创建空链表时,只需要把相应的链表头节点变量设置为空链接即可。在 Python 里可以将其设置为 $None$,其他语言也有类似的惯用值,比如 $NULL$、$nil$、$0$ 等。
5456
5557** 「链节点以及链表的结构定义」** 代码如下:
5658
@@ -69,15 +71,11 @@ class LinkedList:
6971
7072### 2.2 建立一个线性链表
7173
72- 建立一个线性链表的过程是:根据线性表的数据元素动态生成链节点,并依次将其连接到链表中。其做法如下:
73-
74- 1 . 从所给线性表的第 ` 1 ` 个数据元素开始依次获取表中的数据元素。
75- 2 . 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部。
76- 3 . 插入完毕之后返回第 ` 1 ` 个链节点的地址。
77-
78- 建立一个线性链表的时间复杂度为 $O(n)$,$n$ 为线性表长度。
79-
80- ** 「建立一个线性链表」** 的代码如下:
74+ > ** 建立一个线性链表** :根据线性表的数据元素动态生成链节点,并依次将其连接到链表中。
75+ >
76+ > 1 . 从所给线性表的第 $1$ 个数据元素开始依次获取表中的数据元素。
77+ > 2 . 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部。
78+ > 3 . 插入完毕之后返回第 $1$ 个链节点的地址。
8179
8280``` python
8381# 根据 data 初始化一个新链表
@@ -90,15 +88,15 @@ def create(self, data):
9088 cur = cur.next
9189```
9290
93- ### 2.3 求线性链表的长度
91+ 「建立一个线性链表」的操作依赖于线性表的数据元素个数,因此,「建立一个线性链表」的时间复杂度为 $O(n)$,$n$ 为线性表长度。
9492
95- 线性链表的长度被定义为链表中包含的链节点的个数。求线性链表的长度操作只需要使用一个可以顺着链表指针移动的指针变量 ` cur ` 和一个计数器 ` count ` 。具体做法如下:
96-
97- 1 . 让指针变量 ` cur ` 指向链表的第 ` 1 ` 个链节点。
98- 2 . 然后顺着链节点的 ` next ` 指针遍历链表,指针变量 ` cur ` 每指向一个链节点,计数器就做一次计数。
99- 3 . 等 ` cur ` 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。
93+ ### 2.3 求线性链表的长度
10094
101- 求线性链表的长度操作的问题规模是链表的链节点数 $n$,基本操作是 ` cur ` 指针的移动,操作的次数为 $n$,因此算法的时间复杂度为 $O(n)$。
95+ > ** 求线性链表的长度** :使用指针变量 $cur$ 顺着链表 $next$ 指针进行移动,并使用计数器 $count$ 记录元素个数。
96+ >
97+ > 1 . 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。
98+ > 2 . 顺着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。
99+ > 3 . 等 $cur$ 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。
102100
103101** 「求线性链表长度」** 的代码如下:
104102
@@ -113,13 +111,15 @@ def length(self):
113111 return count
114112```
115113
116- ### 2.4 查找元素
117-
118- 在链表中查找值为 ` val ` 的位置:链表不能像数组那样进行随机访问,只能从头节点 ` head ` 开始,沿着链表一个一个节点逐一进行查找。如果查找成功,返回被查找节点的地址。否则返回 ` None ` 。
114+ 「求线性链表长度」的操作依赖于链表的链节点个数,操作的次数为 $n$,因此,「求线性链表长度」的时间复杂度为 $O(n)$,$n$ 为链表长度。
119115
120- 查找元素操作的问题规模是链表的长度 $n$,而基本操作是指针 ` cur ` 的移动操作,所以查找元素算法的时间复杂度为 $O(n)$。
116+ ### 2.4 查找元素
121117
122- ** 「在链表中查找元素」** 的代码如下:
118+ > ** 在链表中查找值为 $val$ 的元素** :从头节点 $head$ 开始,沿着链表节点逐一进行查找。如果查找成功,返回被查找节点的地址;否则返回 $None$。
119+ >
120+ > 1 . 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。
121+ > 2 . 顺着链节点的 $next$ 指针遍历链表,如果遇到 $cur.val == val$,则返回当前指针变量 $cur$。
122+ > 3 . 如果 $cur$ 指向为空时也未找到,则该链表中没有值为 $val$ 的元素,则返回 $None$。
123123
124124``` python
125125# 查找元素
@@ -133,30 +133,28 @@ def find(self, val):
133133 return None
134134```
135135
136+ 「在链表中查找值为 $val$ 的元素」的操作依赖于链表的链节点个数,因此,「「在链表中查找值为 $val$ 的元素」」的时间复杂度为 $O(n)$,$n$ 为链表长度。
137+
136138### 2.5 插入元素
137139
138140链表中插入元素操作分为三种:
139141
140- - ** 链表头部插入元素** :在链表第 ` 1 ` 个链节点之前插入值为 ` val ` 的链节点。
141- - ** 链表尾部插入元素** :在链表最后 ` 1 ` 个链节点之后插入值为 ` val ` 的链节点。
142- - ** 链表中间插入元素** :在链表第 ` i ` 个链节点之前插入值为 ` val ` 的链节点。
142+ - ** 链表头部插入元素** :在链表第 $1$ 个链节点之前插入值为 $ val$ 的链节点。
143+ - ** 链表尾部插入元素** :在链表最后 $1$ 个链节点之后插入值为 $ val$ 的链节点。
144+ - ** 链表中间插入元素** :在链表第 $i$ 个链节点之前插入值为 $ val$ 的链节点。
143145
144146接下来我们分别讲解一下。
145147
146148#### 2.5.1 链表头部插入元素
147149
148- 算法实现的步骤为:
149-
150- 1 . 先创建一个值为 ` val ` 的链节点 ` node ` 。
151- 2 . 然后将 ` node ` 的 ` next ` 指针指向链表的头节点 ` head ` 。
152- 3 . 再将链表的头节点 ` head ` 指向 ` node ` 。
150+ > ** 链表头部插入元素 ** :在链表第 $1$ 个链节点之前插入值为 $val$ 的链节点。
151+ >
152+ > 1 . 先创建一个值为 $ val$ 的链节点 $ node$ 。
153+ > 2 . 然后将 $ node$ 的 $ next$ 指针指向链表的头节点 $ head$ 。
154+ > 3 . 再将链表的头节点 $ head$ 指向 $ node$ 。
153155
154156![ ] ( https://qcdn.itcharge.cn/images/20211208180101.png )
155157
156- 因为在链表头部插入链节点与链表的长度无关,所以该算法的时间复杂度为 $O(1)$。
157-
158- ** 「在链表头部插入值为 ` val ` 元素」** 的代码如下:
159-
160158``` python
161159# 头部插入元素
162160def insertFront (self , val ):
@@ -165,23 +163,21 @@ def insertFront(self, val):
165163 self .head = node
166164```
167165
168- #### 2.5.2 尾部插入元素
166+ 「链表头部插入元素」的操作与链表的长度无关,因此,「链表头部插入元素」的时间复杂度为 $O(1)$。
169167
170- 算法实现的步骤为:
168+ #### 2.5.2 链表尾部插入元素
171169
172- 1 . 先创建一个值为 ` val ` 的链节点 ` node ` 。
173- 2 . 使用指针 ` cur ` 指向链表的头节点 ` head ` 。
174- 3 . 通过链节点的 ` next ` 指针移动 ` cur ` 指针,从而遍历链表,直到 ` cur.next == None ` 。
175- 4 . 令 ` cur.next ` 指向将新的链节点 ` node ` 。
170+ > ** 链表尾部插入元素** :在链表最后 $1$ 个链节点之后插入值为 $val$ 的链节点。
171+ >
172+ > 1 . 先创建一个值为 $val$ 的链节点 $node$。
173+ > 2 . 使用指针 $cur$ 指向链表的头节点 $head$。
174+ > 3 . 通过链节点的 $next$ 指针移动 $cur$ 指针,从而遍历链表,直到 $cur.next$ 为 $None$。
175+ > 4 . 令 $cur.next$ 指向将新的链节点 $node$。
176176
177177![ ] ( https://qcdn.itcharge.cn/images/20211208180111.png )
178178
179- 因为将 ` cur ` 从链表头部移动到尾部的操作次数是 $n$ 次,所以该算法的时间复杂度是 $O(n)$。
180-
181- ** 「在链表尾部插入值为 ` val ` 的元素」** 的代码如下:
182-
183179``` python
184- # 尾部插入元素
180+ # 链表尾部插入元素
185181def insertRear (self , val ):
186182 node = ListNode(val)
187183 cur = self .head
@@ -190,23 +186,21 @@ def insertRear(self, val):
190186 cur.next = node
191187```
192188
193- #### 2.5.3 中间插入元素
189+ 「链表尾部插入元素」的操作需要将 $cur$ 从链表头部移动到尾部,操作次数是 $n$ 次,因此,「链表尾部插入元素」的时间复杂度是 $O(n)$。
194190
195- 算法的实现步骤如下:
191+ #### 2.5.3 链表中间插入元素
196192
197- 1 . 使用指针变量 ` cur ` 和一个计数器 ` count ` 。令 ` cur ` 指向链表的头节点,` count ` 初始值赋值为 ` 0 ` 。
198- 2 . 沿着链节点的 ` next ` 指针遍历链表,指针变量 ` cur ` 每指向一个链节点,计数器就做一次计数。
199- 3 . 当 ` count == index - 1 ` 时,说明遍历到了第 ` index - 1 ` 个链节点,此时停止遍历。
200- 4 . 创建一个值为 ` val ` 的链节点 ` node ` 。
201- 5 . 将 ` node.next ` 指向 ` cur.next ` 。
202- 6 . 然后令 ` cur.next ` 指向 ` node ` 。
193+ > ** 链表中间插入元素** :在链表第 $i$ 个链节点之前插入值为 $val$ 的链节点。
194+ >
195+ > 1 . 使用指针变量 $cur$ 和一个计数器 $count$。令 $cur$ 指向链表的头节点,$count$ 初始值赋值为 $0$。
196+ > 2 . 沿着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。
197+ > 3 . 当遍历到第 $index - 1$ 个链节点时停止遍历。
198+ > 4 . 创建一个值为 $val$ 的链节点 $node$。
199+ > 5 . 将 $node.next$ 指向 $cur.next$。
200+ > 6 . 然后令 $cur.next$ 指向 $node$。
203201
204202![ ] ( https://qcdn.itcharge.cn/images/20211208180121.png )
205203
206- 因为将 ` cur ` 从链表头部移动到第 ` i ` 个链节点之前的操作平均时间复杂度是 $O(n)$,所以该算法的时间复杂度是 $O(n)$。
207-
208- ** 「在链表第 ` i ` 个链节点之前插入值为 ` val ` 的元素」** 的代码如下:
209-
210204``` python
211205# 中间插入元素
212206def insertInside (self , index , val ):
@@ -224,18 +218,16 @@ def insertInside(self, index, val):
224218 cur.next = node
225219```
226220
227- ### 2.6 改变元素
228-
229- 将链表中第 ` i ` 个元素值改为 ` val ` :首先要先遍历到第 ` i ` 个链节点,然后直接更改第 ` i ` 个链节点的元素值。具体做法如下:
230-
231- 1 . 使用指针变量 ` cur ` 和一个计数器 ` count ` 。令 ` cur ` 指向链表的头节点,` count ` 初始值赋值为 ` 0 ` 。
232- 2 . 沿着链节点的 ` next ` 指针遍历链表,指针变量 ` cur ` 每指向一个链节点,计数器就做一次计数。
233- 3 . 当 ` count == index ` 时,说明遍历到了第 ` index ` 个链节点,此时停止遍历。
234- 4 . 直接更改 ` cur ` 的值 ` val ` 。
221+ 「链表中间插入元素」的操作需要将 $cur$ 从链表头部移动到第 $i$ 个链节点之前,操作的平均时间复杂度是 $O(n)$,因此,「链表中间插入元素」的时间复杂度是 $O(n)$。
235222
236- 因为将 ` cur ` 从链表头部移动到第 ` i ` 个链节点的操作平均时间复杂度是 $O(n)$,所以该算法的时间复杂度是 $O(n)$。
223+ ### 2.6 改变元素
237224
238- ** 「将链表中第 ` i ` 个元素值改为 ` val ` 」** 的代码如下:
225+ > ** 将链表中第 $i$ 个元素值改为 $val$** :首先要先遍历到第 $i$ 个链节点,然后直接更改第 $i$ 个链节点的元素值。具体做法如下:
226+ >
227+ > 1 . 使用指针变量 $cur$ 和一个计数器 $count$。令 $cur$ 指向链表的头节点,$count$ 初始值赋值为 $0$。
228+ > 2 . 沿着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。
229+ > 3 . 当遍历到第 $index$ 个链节点时停止遍历。
230+ > 4 . 直接更改 $cur$ 的值 $val$。
239231
240232``` python
241233# 改变元素
@@ -252,48 +244,44 @@ def change(self, index, val):
252244 cur.val = val
253245```
254246
247+ 「将链表中第 $i$ 个元素值改为 $val$」需要将 $cur$ 从链表头部移动到第 $i$ 个链节点,操作的平均时间复杂度是 $O(n)$,因此,「将链表中第 $i$ 个元素值改为 $val$」的时间复杂度是 $O(n)$。
248+
255249### 2.7 删除元素
256250
257- 链表的删除元素操作同样分为三种情况 :
251+ 链表的删除元素操作与链表的查找元素操作一样,同样分为三种情况 :
258252
259- - ** 链表头部删除元素** :删除链表的第 ` 1 ` 个链节点。
260- - ** 链表尾部删除元素** :删除链表末尾最后 ` 1 ` 个链节点。
261- - ** 链表中间删除元素** :删除链表第 ` i ` 个链节点。
253+ - ** 链表头部删除元素** :删除链表的第 $1$ 个链节点。
254+ - ** 链表尾部删除元素** :删除链表末尾最后 $1$ 个链节点。
255+ - ** 链表中间删除元素** :删除链表第 $i$ 个链节点。
262256
263257接下来我们分别讲解一下。
264258
265259#### 2.7.1 链表头部删除元素
266260
267- 链表头部删除元素的方法很简单,具体步骤如下:
268-
269- - 直接将 ` self.head ` 沿着 ` next ` 指针向右移动一步即可。
261+ > ** 链表头部删除元素 ** :删除链表的第 $1$ 个链节点。
262+ >
263+ > 1 . 直接将 $ self.head$ 沿着 $ next$ 指针向右移动一步即可。
270264
271265![ ] ( https://qcdn.itcharge.cn/images/20211208180131.png )
272266
273- 因为只涉及到 ` 1 ` 步移动操作,所以此算法的时间复杂度为 $O(1)$。
274-
275- ** 「链表头部删除元素」** 的代码如下所示:
276-
277267``` python
278268# 链表头部删除元素
279269def removeFront (self ):
280270 if self .head:
281271 self .head = self .head.next
282272```
283273
284- #### 2.7.2 链表尾部删除元素
274+ 「链表头部删除元」只涉及到 $1$ 步移动操作,因此,「链表头部删除元素」的时间复杂度为 $O(1)$。
285275
286- 链表尾部删除元素的方法也比较简单,具体步骤如下:
276+ #### 2.7.2 链表尾部删除元素
287277
288- - 先使用指针变量 ` cur ` 沿着 ` next ` 指针移动到倒数第 ` 2 ` 个链节点。
289- - 然后将此节点的 ` next ` 指针指向 ` None ` 即可。
278+ > ** 链表尾部删除元素** :删除链表末尾最后 $1$ 个链节点。
279+ >
280+ > 1 . 先使用指针变量 $cur$ 沿着 $next$ 指针移动到倒数第 $2$ 个链节点。
281+ > 2 . 然后将此节点的 $next$ 指针指向 $None$ 即可。
290282
291283![ ] ( https://qcdn.itcharge.cn/images/20211208180138.png )
292284
293- 因为移动到链表尾部的操作次数为 $n$ 次,所以该算法的时间复杂度为 $O(n)$。
294-
295- ** 「链表尾部删除元素」** 的代码如下所示:
296-
297285``` python
298286# 链表尾部删除元素
299287def removeRear (self ):
@@ -306,17 +294,17 @@ def removeRear(self):
306294 cur.next = None
307295```
308296
309- #### 2.7.3 链表中间删除元素
297+ 「链表尾部删除元素」的操作涉及到移动到链表尾部,操作次数为 $n - 2$ 次,因此,「链表尾部删除元素」的时间复杂度为 $O(n)$。
310298
311- 删除链表中第 ` i ` 个元素的算法具体步骤如下:
299+ #### 2.7.3 链表中间删除元素
312300
313- 1 . 先使用指针变量 ` cur ` 移动到第 ` i - 1 ` 个位置的链节点。
314- 2 . 然后将 ` cur ` 的 ` next ` 指针,指向要第 ` i ` 个元素的下一个节点即可。
301+ > ** 链表中间删除元素** :删除链表第 $i$ 个链节点。
302+ >
303+ > 1 . 先使用指针变量 $cur$ 移动到第 $i - 1$ 个位置的链节点。
304+ > 2 . 然后将 $cur$ 的 $next$ 指针,指向要第 $i$ 个元素的下一个节点即可。
315305
316306![ ] ( https://qcdn.itcharge.cn/images/20211208180144.png )
317307
318- ** 「删除链表中第 ` i ` 个元素」** 的代码如下所示:
319-
320308``` python
321309# 链表中间删除元素
322310def removeInside (self , index ):
@@ -334,6 +322,8 @@ def removeInside(self, index):
334322 cur.next = del_node.next
335323```
336324
325+ 「链表中间删除元素」的操作需要将 $cur$ 从链表头部移动到第 $i$ 个链节点之前,操作的平均时间复杂度是 $O(n)$,因此,「链表中间删除元素」的时间复杂度是 $O(n)$。
326+
337327---
338328
339329到这里,有关链表的基础知识就介绍完了。下面进行一下总结。
@@ -342,7 +332,12 @@ def removeInside(self, index):
342332
343333链表是最基础、最简单的数据结构。** 「链表」** 是实现线性表的链式存储结构的基础。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。
344334
345- 链表最大的优点在于可以灵活的添加和删除元素。链表进行访问元素、改变元素操作的时间复杂度为 $O(n)$,进行头部插入、头部删除元素操作的时间复杂度是 $O(1)$,进行尾部插入、尾部删除操作的时间复杂度是 $O(n)$。普通情况下进行插入、删除元素操作的时间复杂度为 $O(n)$。
335+ 链表最大的优点在于可以灵活的添加和删除元素。
336+
337+ - 链表进行访问元素、改变元素操作的时间复杂度为 $O(n)$。
338+ - 链表进行头部插入、头部删除元素操作的时间复杂度是 $O(1)$。
339+ - 链表进行尾部插入、尾部删除操作的时间复杂度是 $O(n)$。
340+ - 链表在普通情况下进行插入、删除元素操作的时间复杂度为 $O(n)$。
346341
347342## 参考资料
348343
0 commit comments