mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-12 07:26:07 +00:00
Polish the contents
1. Array and linked list. 2. Computational complexity. 3. Fix a mistake in counting_sort.md
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
# 链表
|
||||
|
||||
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间往往是散落在内存各处的。上节讲到,**存储数组的内存空间必须是连续的**,当我们需要申请一个非常大的数组时,系统不一定能够分配这么大的连续内存空间。
|
||||
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。
|
||||
|
||||
相对地,链表则更加灵活,可以被存储到非连续的内存空间。「链表 Linked List」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(即结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加灵活,系统可将结点分散在内存各处,而不必保证内存地址的连续性。
|
||||
与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构,其每个元素都是一个结点对象,各个结点之间通过指针连接,从当前结点通过指针可以访问到下一个结点。由于指针记录了下个结点的内存地址,因此无需保证内存地址的连续性,从而可以将各个结点分散存储在内存各处。
|
||||
|
||||
链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。
|
||||
链表「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」,或称指向下一结点的「引用 Reference」。
|
||||
|
||||

|
||||
|
||||
@@ -154,13 +154,15 @@
|
||||
}
|
||||
```
|
||||
|
||||
**尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。
|
||||
!!! question "尾结点指向什么?"
|
||||
|
||||
**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。
|
||||
我们将链表的最后一个结点称为「尾结点」,其指向的是“空”,在 Java, C++, Python 中分别记为 `null`, `nullptr`, `None` 。在不引起歧义的前提下,本书都使用 `null` 来表示空。
|
||||
|
||||
!!! tip
|
||||
!!! question "如何称呼链表?"
|
||||
|
||||
我们通常将头结点当作链表的代称,例如头结点 `head` 和链表 `head` 实际上是同义的。
|
||||
在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个结点对象组成,我们通常将头结点当作链表的代称,例如头结点 `head` 和链表 `head` 实际上是同义的。
|
||||
|
||||
**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的头结点(即首个结点)出发,通过指针 `next` 依次访问所有结点。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -223,7 +225,6 @@
|
||||
n2 := NewListNode(2)
|
||||
n3 := NewListNode(5)
|
||||
n4 := NewListNode(4)
|
||||
|
||||
// 构建引用指向
|
||||
n0.Next = n1
|
||||
n1.Next = n2
|
||||
@@ -335,7 +336,7 @@
|
||||
|
||||
## 链表优点
|
||||
|
||||
**在链表中,插入与删除结点的操作效率高**。比如,如果我们想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。
|
||||
**链表中插入与删除结点的操作效率高**。例如,如果我们想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
|
||||
|
||||

|
||||
|
||||
@@ -399,7 +400,7 @@
|
||||
[class]{}-[func]{insert}
|
||||
```
|
||||
|
||||
在链表中删除结点也很方便,只需要改变一个结点指针即可。如下图所示,虽然在完成删除后结点 `P` 仍然指向 `n1` ,但实际上 `P` 已经不属于此链表了,因为遍历此链表是无法访问到 `P` 的。
|
||||
在链表中删除结点也非常方便,只需改变一个结点的指针即可。如下图所示,尽管在删除操作完成后,结点 `P` 仍然指向 `n1`,但实际上 `P` 已经不再属于此链表,因为遍历此链表时无法访问到 `P`。
|
||||
|
||||

|
||||
|
||||
@@ -465,7 +466,7 @@
|
||||
|
||||
## 链表缺点
|
||||
|
||||
**链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。
|
||||
**链表访问结点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意结点,这是因为系统需要从头结点出发,逐个向后遍历直至找到目标结点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的结点,则需要向后遍历 `index` 轮。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -527,7 +528,7 @@
|
||||
[class]{}-[func]{access}
|
||||
```
|
||||
|
||||
**链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。
|
||||
**链表的内存占用较大**。链表以结点为单位,每个结点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
|
||||
|
||||
## 链表常用操作
|
||||
|
||||
@@ -595,11 +596,11 @@
|
||||
|
||||
## 常见链表类型
|
||||
|
||||
**单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。
|
||||
**单向链表**。即上述介绍的普通链表。单向链表的结点包含值和指向下一结点的指针(引用)两项数据。我们将首个结点称为头结点,将最后一个结点成为尾结点,尾结点指向 `null` 。
|
||||
|
||||
**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。
|
||||
**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,任意结点都可以视作头结点。
|
||||
|
||||
**双向链表**。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。
|
||||
**双向链表**。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的结点定义同时包含指向后继结点(下一结点)和前驱结点(上一结点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user