Skip to content

Commit b08137d

Browse files
committed
Update 01.Linked-List-Basic.md
1 parent 2f9ee06 commit b08137d

File tree

1 file changed

+95
-100
lines changed

1 file changed

+95
-100
lines changed

Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md

Lines changed: 95 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
![](https://qcdn.itcharge.cn/images/20211208180037.png)
1212

13-
如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 `next`」。
13+
如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 $next$」。
1414

1515
在链表中,数据元素之间的逻辑关系是通过指针来间接反映的。逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。
1616

@@ -20,37 +20,39 @@
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
# 头部插入元素
162160
def 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+
# 链表尾部插入元素
185181
def 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
# 中间插入元素
212206
def 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
# 链表头部删除元素
279269
def 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
# 链表尾部删除元素
299287
def 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
# 链表中间删除元素
322310
def 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

Comments
 (0)