10
10
11
11
![ ] ( https://qcdn.itcharge.cn/images/20211208180037.png )
12
12
13
- 如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 ` next ` 」。
13
+ 如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 $ next$ 」。
14
14
15
15
在链表中,数据元素之间的逻辑关系是通过指针来间接反映的。逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。
16
16
20
20
21
21
- ** 缺点** :不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。
22
22
23
- 接下来先来介绍一下除了单链表之外 ,链表的其他几种类型。
23
+ 接下来我们来介绍一下除了单链表之外 ,链表的其他几种类型。
24
24
25
25
### 1.2 双向链表
26
26
27
27
> ** 双向链表(Doubly Linked List)** :链表的一种,也叫做双链表。它的每个链节点中有两个指针,分别指向直接后继和直接前驱。
28
28
29
- 从双链表的任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。
29
+ - ** 双向链表特点 ** : 从双链表的任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。
30
30
31
31
![ ] ( https://qcdn.itcharge.cn/images/20211208103220.png )
32
32
33
33
### 1.3 循环链表
34
34
35
35
> ** 循环链表(Circular linked list)** :链表的一种。它的最后一个链节点指向头节点,形成一个环。
36
36
37
- 从循环链表的任何一个节点出发都能找到任何其他节点。
37
+ - ** 循环链表特点 ** : 从循环链表的任何一个节点出发都能找到任何其他节点。
38
38
39
39
![ ] ( https://qcdn.itcharge.cn/images/20211208180048.png )
40
40
41
- 接下来我们以单链表为例 ,介绍一下链表的基本操作。
41
+ 接下来我们以最基本的「单链表」为例 ,介绍一下链表的基本操作。
42
42
43
43
## 2. 链表的基本操作
44
44
45
45
数据结构的操作一般涉及到增、删、改、查 4 种情况,链表的操作也基本上是这 4 种情况。我们一起来看一下链表的基本操作。
46
46
47
47
### 2.1 链表的结构定义
48
48
49
- 链表是由链节点通过 ` next ` 链接而构成的,所以先来定义一个简单的链节点类,即 ` ListNode ` 类。 ` ListNode ` 类使用成员变量 ` val ` 表示数据元素的值,使用指针变量 ` next ` 表示后继指针 。
49
+ 链表是由链节点通过 $ next$ 链接而构成的,我们可以先定义一个简单的「链节点类」,再来定义完整的「链表类」 。
50
50
51
- 然后再定义链表类,即 ` LinkedList ` 类。 ` LinkedList ` 类中只有一个链节点变量 ` head ` 用来表示链表的头节点 。
51
+ - ** 链节点类(即 ListNode 类) ** :使用成员变量 $val$ 表示数据元素的值,使用指针变量 $next$ 表示后继指针 。
52
52
53
- 我们在创建空链表时,只需要把相应的链表头节点变量设置为空链接即可。在 ` Python ` 里可以将其设置为 ` None ` ,其他语言也有类似的惯用值,比如 ` NULL ` 、` nil ` 、` 0 ` 等。
53
+ - ** 链表类(即 LinkedList 类)** :使用一个链节点变量 $head$ 来表示链表的头节点。
54
+
55
+ 我们在创建空链表时,只需要把相应的链表头节点变量设置为空链接即可。在 Python 里可以将其设置为 $None$,其他语言也有类似的惯用值,比如 $NULL$、$nil$、$0$ 等。
54
56
55
57
** 「链节点以及链表的结构定义」** 代码如下:
56
58
@@ -69,15 +71,11 @@ class LinkedList:
69
71
70
72
### 2.2 建立一个线性链表
71
73
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$ 个链节点的地址。
81
79
82
80
``` python
83
81
# 根据 data 初始化一个新链表
@@ -90,15 +88,15 @@ def create(self, data):
90
88
cur = cur.next
91
89
```
92
90
93
- ### 2.3 求线性链表的长度
91
+ 「建立一个线性链表」的操作依赖于线性表的数据元素个数,因此,「建立一个线性链表」的时间复杂度为 $O(n)$,$n$ 为线性表长度。
94
92
95
- 线性链表的长度被定义为链表中包含的链节点的个数。求线性链表的长度操作只需要使用一个可以顺着链表指针移动的指针变量 ` cur ` 和一个计数器 ` count ` 。具体做法如下:
96
-
97
- 1 . 让指针变量 ` cur ` 指向链表的第 ` 1 ` 个链节点。
98
- 2 . 然后顺着链节点的 ` next ` 指针遍历链表,指针变量 ` cur ` 每指向一个链节点,计数器就做一次计数。
99
- 3 . 等 ` cur ` 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。
93
+ ### 2.3 求线性链表的长度
100
94
101
- 求线性链表的长度操作的问题规模是链表的链节点数 $n$,基本操作是 ` cur ` 指针的移动,操作的次数为 $n$,因此算法的时间复杂度为 $O(n)$。
95
+ > ** 求线性链表的长度** :使用指针变量 $cur$ 顺着链表 $next$ 指针进行移动,并使用计数器 $count$ 记录元素个数。
96
+ >
97
+ > 1 . 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。
98
+ > 2 . 顺着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。
99
+ > 3 . 等 $cur$ 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。
102
100
103
101
** 「求线性链表长度」** 的代码如下:
104
102
@@ -113,13 +111,15 @@ def length(self):
113
111
return count
114
112
```
115
113
116
- ### 2.4 查找元素
117
-
118
- 在链表中查找值为 ` val ` 的位置:链表不能像数组那样进行随机访问,只能从头节点 ` head ` 开始,沿着链表一个一个节点逐一进行查找。如果查找成功,返回被查找节点的地址。否则返回 ` None ` 。
114
+ 「求线性链表长度」的操作依赖于链表的链节点个数,操作的次数为 $n$,因此,「求线性链表长度」的时间复杂度为 $O(n)$,$n$ 为链表长度。
119
115
120
- 查找元素操作的问题规模是链表的长度 $n$,而基本操作是指针 ` cur ` 的移动操作,所以查找元素算法的时间复杂度为 $O(n)$。
116
+ ### 2.4 查找元素
121
117
122
- ** 「在链表中查找元素」** 的代码如下:
118
+ > ** 在链表中查找值为 $val$ 的元素** :从头节点 $head$ 开始,沿着链表节点逐一进行查找。如果查找成功,返回被查找节点的地址;否则返回 $None$。
119
+ >
120
+ > 1 . 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。
121
+ > 2 . 顺着链节点的 $next$ 指针遍历链表,如果遇到 $cur.val == val$,则返回当前指针变量 $cur$。
122
+ > 3 . 如果 $cur$ 指向为空时也未找到,则该链表中没有值为 $val$ 的元素,则返回 $None$。
123
123
124
124
``` python
125
125
# 查找元素
@@ -133,30 +133,28 @@ def find(self, val):
133
133
return None
134
134
```
135
135
136
+ 「在链表中查找值为 $val$ 的元素」的操作依赖于链表的链节点个数,因此,「「在链表中查找值为 $val$ 的元素」」的时间复杂度为 $O(n)$,$n$ 为链表长度。
137
+
136
138
### 2.5 插入元素
137
139
138
140
链表中插入元素操作分为三种:
139
141
140
- - ** 链表头部插入元素** :在链表第 ` 1 ` 个链节点之前插入值为 ` val ` 的链节点。
141
- - ** 链表尾部插入元素** :在链表最后 ` 1 ` 个链节点之后插入值为 ` val ` 的链节点。
142
- - ** 链表中间插入元素** :在链表第 ` i ` 个链节点之前插入值为 ` val ` 的链节点。
142
+ - ** 链表头部插入元素** :在链表第 $1$ 个链节点之前插入值为 $ val$ 的链节点。
143
+ - ** 链表尾部插入元素** :在链表最后 $1$ 个链节点之后插入值为 $ val$ 的链节点。
144
+ - ** 链表中间插入元素** :在链表第 $i$ 个链节点之前插入值为 $ val$ 的链节点。
143
145
144
146
接下来我们分别讲解一下。
145
147
146
148
#### 2.5.1 链表头部插入元素
147
149
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$ 。
153
155
154
156
![ ] ( https://qcdn.itcharge.cn/images/20211208180101.png )
155
157
156
- 因为在链表头部插入链节点与链表的长度无关,所以该算法的时间复杂度为 $O(1)$。
157
-
158
- ** 「在链表头部插入值为 ` val ` 元素」** 的代码如下:
159
-
160
158
``` python
161
159
# 头部插入元素
162
160
def insertFront (self , val ):
@@ -165,23 +163,21 @@ def insertFront(self, val):
165
163
self .head = node
166
164
```
167
165
168
- #### 2.5.2 尾部插入元素
166
+ 「链表头部插入元素」的操作与链表的长度无关,因此,「链表头部插入元素」的时间复杂度为 $O(1)$。
169
167
170
- 算法实现的步骤为:
168
+ #### 2.5.2 链表尾部插入元素
171
169
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$。
176
176
177
177
![ ] ( https://qcdn.itcharge.cn/images/20211208180111.png )
178
178
179
- 因为将 ` cur ` 从链表头部移动到尾部的操作次数是 $n$ 次,所以该算法的时间复杂度是 $O(n)$。
180
-
181
- ** 「在链表尾部插入值为 ` val ` 的元素」** 的代码如下:
182
-
183
179
``` python
184
- # 尾部插入元素
180
+ # 链表尾部插入元素
185
181
def insertRear (self , val ):
186
182
node = ListNode(val)
187
183
cur = self .head
@@ -190,23 +186,21 @@ def insertRear(self, val):
190
186
cur.next = node
191
187
```
192
188
193
- #### 2.5.3 中间插入元素
189
+ 「链表尾部插入元素」的操作需要将 $cur$ 从链表头部移动到尾部,操作次数是 $n$ 次,因此,「链表尾部插入元素」的时间复杂度是 $O(n)$。
194
190
195
- 算法的实现步骤如下:
191
+ #### 2.5.3 链表中间插入元素
196
192
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$。
203
201
204
202
![ ] ( https://qcdn.itcharge.cn/images/20211208180121.png )
205
203
206
- 因为将 ` cur ` 从链表头部移动到第 ` i ` 个链节点之前的操作平均时间复杂度是 $O(n)$,所以该算法的时间复杂度是 $O(n)$。
207
-
208
- ** 「在链表第 ` i ` 个链节点之前插入值为 ` val ` 的元素」** 的代码如下:
209
-
210
204
``` python
211
205
# 中间插入元素
212
206
def insertInside (self , index , val ):
@@ -224,18 +218,16 @@ def insertInside(self, index, val):
224
218
cur.next = node
225
219
```
226
220
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)$。
235
222
236
- 因为将 ` cur ` 从链表头部移动到第 ` i ` 个链节点的操作平均时间复杂度是 $O(n)$,所以该算法的时间复杂度是 $O(n)$。
223
+ ### 2.6 改变元素
237
224
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$。
239
231
240
232
``` python
241
233
# 改变元素
@@ -252,48 +244,44 @@ def change(self, index, val):
252
244
cur.val = val
253
245
```
254
246
247
+ 「将链表中第 $i$ 个元素值改为 $val$」需要将 $cur$ 从链表头部移动到第 $i$ 个链节点,操作的平均时间复杂度是 $O(n)$,因此,「将链表中第 $i$ 个元素值改为 $val$」的时间复杂度是 $O(n)$。
248
+
255
249
### 2.7 删除元素
256
250
257
- 链表的删除元素操作同样分为三种情况 :
251
+ 链表的删除元素操作与链表的查找元素操作一样,同样分为三种情况 :
258
252
259
- - ** 链表头部删除元素** :删除链表的第 ` 1 ` 个链节点。
260
- - ** 链表尾部删除元素** :删除链表末尾最后 ` 1 ` 个链节点。
261
- - ** 链表中间删除元素** :删除链表第 ` i ` 个链节点。
253
+ - ** 链表头部删除元素** :删除链表的第 $1$ 个链节点。
254
+ - ** 链表尾部删除元素** :删除链表末尾最后 $1$ 个链节点。
255
+ - ** 链表中间删除元素** :删除链表第 $i$ 个链节点。
262
256
263
257
接下来我们分别讲解一下。
264
258
265
259
#### 2.7.1 链表头部删除元素
266
260
267
- 链表头部删除元素的方法很简单,具体步骤如下:
268
-
269
- - 直接将 ` self.head ` 沿着 ` next ` 指针向右移动一步即可。
261
+ > ** 链表头部删除元素 ** :删除链表的第 $1$ 个链节点。
262
+ >
263
+ > 1 . 直接将 $ self.head$ 沿着 $ next$ 指针向右移动一步即可。
270
264
271
265
![ ] ( https://qcdn.itcharge.cn/images/20211208180131.png )
272
266
273
- 因为只涉及到 ` 1 ` 步移动操作,所以此算法的时间复杂度为 $O(1)$。
274
-
275
- ** 「链表头部删除元素」** 的代码如下所示:
276
-
277
267
``` python
278
268
# 链表头部删除元素
279
269
def removeFront (self ):
280
270
if self .head:
281
271
self .head = self .head.next
282
272
```
283
273
284
- #### 2.7.2 链表尾部删除元素
274
+ 「链表头部删除元」只涉及到 $1$ 步移动操作,因此,「链表头部删除元素」的时间复杂度为 $O(1)$。
285
275
286
- 链表尾部删除元素的方法也比较简单,具体步骤如下:
276
+ #### 2.7.2 链表尾部删除元素
287
277
288
- - 先使用指针变量 ` cur ` 沿着 ` next ` 指针移动到倒数第 ` 2 ` 个链节点。
289
- - 然后将此节点的 ` next ` 指针指向 ` None ` 即可。
278
+ > ** 链表尾部删除元素** :删除链表末尾最后 $1$ 个链节点。
279
+ >
280
+ > 1 . 先使用指针变量 $cur$ 沿着 $next$ 指针移动到倒数第 $2$ 个链节点。
281
+ > 2 . 然后将此节点的 $next$ 指针指向 $None$ 即可。
290
282
291
283
![ ] ( https://qcdn.itcharge.cn/images/20211208180138.png )
292
284
293
- 因为移动到链表尾部的操作次数为 $n$ 次,所以该算法的时间复杂度为 $O(n)$。
294
-
295
- ** 「链表尾部删除元素」** 的代码如下所示:
296
-
297
285
``` python
298
286
# 链表尾部删除元素
299
287
def removeRear (self ):
@@ -306,17 +294,17 @@ def removeRear(self):
306
294
cur.next = None
307
295
```
308
296
309
- #### 2.7.3 链表中间删除元素
297
+ 「链表尾部删除元素」的操作涉及到移动到链表尾部,操作次数为 $n - 2$ 次,因此,「链表尾部删除元素」的时间复杂度为 $O(n)$。
310
298
311
- 删除链表中第 ` i ` 个元素的算法具体步骤如下:
299
+ #### 2.7.3 链表中间删除元素
312
300
313
- 1 . 先使用指针变量 ` cur ` 移动到第 ` i - 1 ` 个位置的链节点。
314
- 2 . 然后将 ` cur ` 的 ` next ` 指针,指向要第 ` i ` 个元素的下一个节点即可。
301
+ > ** 链表中间删除元素** :删除链表第 $i$ 个链节点。
302
+ >
303
+ > 1 . 先使用指针变量 $cur$ 移动到第 $i - 1$ 个位置的链节点。
304
+ > 2 . 然后将 $cur$ 的 $next$ 指针,指向要第 $i$ 个元素的下一个节点即可。
315
305
316
306
![ ] ( https://qcdn.itcharge.cn/images/20211208180144.png )
317
307
318
- ** 「删除链表中第 ` i ` 个元素」** 的代码如下所示:
319
-
320
308
``` python
321
309
# 链表中间删除元素
322
310
def removeInside (self , index ):
@@ -334,6 +322,8 @@ def removeInside(self, index):
334
322
cur.next = del_node.next
335
323
```
336
324
325
+ 「链表中间删除元素」的操作需要将 $cur$ 从链表头部移动到第 $i$ 个链节点之前,操作的平均时间复杂度是 $O(n)$,因此,「链表中间删除元素」的时间复杂度是 $O(n)$。
326
+
337
327
---
338
328
339
329
到这里,有关链表的基础知识就介绍完了。下面进行一下总结。
@@ -342,7 +332,12 @@ def removeInside(self, index):
342
332
343
333
链表是最基础、最简单的数据结构。** 「链表」** 是实现线性表的链式存储结构的基础。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。
344
334
345
- 链表最大的优点在于可以灵活的添加和删除元素。链表进行访问元素、改变元素操作的时间复杂度为 $O(n)$,进行头部插入、头部删除元素操作的时间复杂度是 $O(1)$,进行尾部插入、尾部删除操作的时间复杂度是 $O(n)$。普通情况下进行插入、删除元素操作的时间复杂度为 $O(n)$。
335
+ 链表最大的优点在于可以灵活的添加和删除元素。
336
+
337
+ - 链表进行访问元素、改变元素操作的时间复杂度为 $O(n)$。
338
+ - 链表进行头部插入、头部删除元素操作的时间复杂度是 $O(1)$。
339
+ - 链表进行尾部插入、尾部删除操作的时间复杂度是 $O(n)$。
340
+ - 链表在普通情况下进行插入、删除元素操作的时间复杂度为 $O(n)$。
346
341
347
342
## 参考资料
348
343
0 commit comments