|
|
@ -1,10 +1,10 @@
|
|
|
|
# 链表
|
|
|
|
# 链表
|
|
|
|
|
|
|
|
|
|
|
|
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。
|
|
|
|
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。
|
|
|
|
|
|
|
|
|
|
|
|
「链表 Linked List」是一种线性数据结构,其每个元素都是一个节点对象,各个节点之间通过指针连接,从当前节点通过指针可以访问到下一个节点。**由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性**,从而可以将各个节点分散存储在内存各处。
|
|
|
|
「链表 Linked List」是一种线性数据结构,其每个元素都是一个节点对象,各个节点之间通过指针连接,从当前节点通过指针可以访问到下一个节点。**由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性**,从而可以将各个节点分散存储在内存各处。
|
|
|
|
|
|
|
|
|
|
|
|
链表「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称「引用 Reference」。
|
|
|
|
链表中的「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称「引用 Reference」。
|
|
|
|
|
|
|
|
|
|
|
|
![链表定义与存储方式](linked_list.assets/linkedlist_definition.png)
|
|
|
|
![链表定义与存储方式](linked_list.assets/linkedlist_definition.png)
|
|
|
|
|
|
|
|
|
|
|
@ -169,13 +169,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
!!! question "尾节点指向什么?"
|
|
|
|
我们将链表的首个节点称为「头节点」,最后一个节点称为「尾节点」。尾节点指向的是“空”,在 Java, C++, Python 中分别记为 $\text{null}$ , $\text{nullptr}$ , $\text{None}$ 。在不引起歧义的前提下,本书都使用 $\text{None}$ 来表示空。
|
|
|
|
|
|
|
|
|
|
|
|
我们将链表的最后一个节点称为「尾节点」,其指向的是“空”,在 Java, C++, Python 中分别记为 $\text{null}$ , $\text{nullptr}$ , $\text{None}$ 。在不引起歧义的前提下,本书都使用 $\text{None}$ 来表示空。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!!! question "如何称呼链表?"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个节点对象组成,我们通常将头节点当作链表的代称,例如头节点 `head` 和链表 `head` 实际上是同义的。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**链表初始化方法**。建立链表分为两步,第一步是初始化各个节点对象,第二步是构建引用指向关系。完成后,即可以从链表的头节点(即首个节点)出发,通过指针 `next` 依次访问所有节点。
|
|
|
|
**链表初始化方法**。建立链表分为两步,第一步是初始化各个节点对象,第二步是构建引用指向关系。完成后,即可以从链表的头节点(即首个节点)出发,通过指针 `next` 依次访问所有节点。
|
|
|
|
|
|
|
|
|
|
|
@ -372,9 +366,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在编程语言中,数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` , `nums[1]` 等。而链表是由多个分散的节点对象组成,**我们通常将头节点当作链表的代称**,比如以上代码中的链表可被记做链表 `n0` 。
|
|
|
|
|
|
|
|
|
|
|
|
## 链表优点
|
|
|
|
## 链表优点
|
|
|
|
|
|
|
|
|
|
|
|
**链表中插入与删除节点的操作效率高**。例如,如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
|
|
|
|
**链表中插入与删除节点的操作效率高**。如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
|
|
|
|
|
|
|
|
|
|
|
|
![链表插入节点](linked_list.assets/linkedlist_insert_node.png)
|
|
|
|
![链表插入节点](linked_list.assets/linkedlist_insert_node.png)
|
|
|
|
|
|
|
|
|
|
|
@ -528,7 +524,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
## 链表缺点
|
|
|
|
## 链表缺点
|
|
|
|
|
|
|
|
|
|
|
|
**链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意节点,这是因为系统需要从头节点出发,逐个向后遍历直至找到目标节点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的节点,则需要向后遍历 `index` 轮。
|
|
|
|
**链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而链表无法直接访问任意节点,因为程序需要从头节点出发,逐个向后遍历,直至找到目标节点。也就是说,如果想要访问链表中第 $i$ 个节点,则需要向后遍历 $i - 1$ 轮。
|
|
|
|
|
|
|
|
|
|
|
|
=== "Java"
|
|
|
|
=== "Java"
|
|
|
|
|
|
|
|
|
|
|
@ -602,7 +598,7 @@
|
|
|
|
[class]{}-[func]{access}
|
|
|
|
[class]{}-[func]{access}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**链表的内存占用较大**。链表以节点为单位,每个节点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
|
|
|
|
**链表的内存占用较大**。链表以节点为单位,每个节点除了包含值,还需额外保存下一节点的引用(指针)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
|
|
|
|
|
|
|
|
|
|
|
|
## 链表常用操作
|
|
|
|
## 链表常用操作
|
|
|
|
|
|
|
|
|
|
|
|