refactor: Replace 结点 with 节点 (#452)

* Replace 结点 with 节点
Update the footnotes in the figures

* Update mindmap

* Reduce the size of the mindmap.png
This commit is contained in:
Yudong Jin
2023-04-09 04:32:17 +08:00
committed by GitHub
parent 3f4e32b2b0
commit 1c8b7ef559
395 changed files with 2056 additions and 2056 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -2,19 +2,19 @@
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。
与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构其每个元素都是一个点对象,各个点之间通过指针连接,从当前点通过指针可以访问到下一个点。由于指针记录了下个点的内存地址,因此无需保证内存地址的连续性,从而可以将各个点分散存储在内存各处。
与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构其每个元素都是一个点对象,各个点之间通过指针连接,从当前点通过指针可以访问到下一个点。由于指针记录了下个点的内存地址,因此无需保证内存地址的连续性,从而可以将各个点分散存储在内存各处。
链表「点 Node」包含两项数据一是点「值 Value」二是指向下一点的「指针 Pointer」或称指向下一点的「引用 Reference」。
链表「点 Node」包含两项数据一是点「值 Value」二是指向下一点的「指针 Pointer」或称指向下一点的「引用 Reference」。
![链表定义与存储方式](linked_list.assets/linkedlist_definition.png)
=== "Java"
```java title=""
/* 链表点类 */
/* 链表点类 */
class ListNode {
int val; // 点值
ListNode next; // 指向下一点的指针(引用)
int val; // 点值
ListNode next; // 指向下一点的指针(引用)
ListNode(int x) { val = x; } // 构造函数
}
```
@@ -22,10 +22,10 @@
=== "C++"
```cpp title=""
/* 链表点结构体 */
/* 链表点结构体 */
struct ListNode {
int val; // 点值
ListNode *next; // 指向下一点的指针(引用)
int val; // 点值
ListNode *next; // 指向下一点的指针(引用)
ListNode(int x) : val(x), next(nullptr) {} // 构造函数
};
```
@@ -33,20 +33,20 @@
=== "Python"
```python title=""
""" 链表点类 """
""" 链表点类 """
class ListNode:
def __init__(self, val: int):
self.val: int = val # 点值
self.next: Optional[ListNode] = None # 指向下一点的指针(引用)
self.val: int = val # 点值
self.next: Optional[ListNode] = None # 指向下一点的指针(引用)
```
=== "Go"
```go title=""
/* 链表点结构体 */
/* 链表点结构体 */
type ListNode struct {
Val int // 点值
Next *ListNode // 指向下一点的指针(引用)
Val int // 点值
Next *ListNode // 指向下一点的指针(引用)
}
// NewListNode 构造函数,创建一个新的链表
@@ -61,13 +61,13 @@
=== "JavaScript"
```javascript title=""
/* 链表点类 */
/* 链表点类 */
class ListNode {
val;
next;
constructor(val, next) {
this.val = (val === undefined ? 0 : val); // 点值
this.next = (next === undefined ? null : next); // 指向下一点的引用
this.val = (val === undefined ? 0 : val); // 点值
this.next = (next === undefined ? null : next); // 指向下一点的引用
}
}
```
@@ -75,13 +75,13 @@
=== "TypeScript"
```typescript title=""
/* 链表点类 */
/* 链表点类 */
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val; // 点值
this.next = next === undefined ? null : next; // 指向下一点的引用
this.val = val === undefined ? 0 : val; // 点值
this.next = next === undefined ? null : next; // 指向下一点的引用
}
}
```
@@ -89,16 +89,16 @@
=== "C"
```c title=""
/* 链表点结构体 */
/* 链表点结构体 */
struct ListNode {
int val; // 点值
struct ListNode *next; // 指向下一点的指针(引用)
int val; // 点值
struct ListNode *next; // 指向下一点的指针(引用)
};
// typedef 作用是为一种数据类型定义一个新名字
typedef struct ListNode ListNode;
/* 构造函数,初始化一个新点 */
/* 构造函数,初始化一个新点 */
ListNode *newListNode(int val) {
ListNode *node, *next;
node = (ListNode *) malloc(sizeof(ListNode));
@@ -111,11 +111,11 @@
=== "C#"
```csharp title=""
/* 链表点类 */
/* 链表点类 */
class ListNode
{
int val; // 点值
ListNode next; // 指向下一点的引用
int val; // 点值
ListNode next; // 指向下一点的引用
ListNode(int x) => val = x; //构造函数
}
```
@@ -123,10 +123,10 @@
=== "Swift"
```swift title=""
/* 链表点类 */
/* 链表点类 */
class ListNode {
var val: Int // 点值
var next: ListNode? // 指向下一点的指针(引用)
var val: Int // 点值
var next: ListNode? // 指向下一点的指针(引用)
init(x: Int) { // 构造函数
val = x
@@ -137,13 +137,13 @@
=== "Zig"
```zig title=""
// 链表点类
// 链表点类
pub fn ListNode(comptime T: type) type {
return struct {
const Self = @This();
val: T = 0, // 点值
next: ?*Self = null, // 指向下一点的指针(引用)
val: T = 0, // 点值
next: ?*Self = null, // 指向下一点的指针(引用)
// 构造函数
pub fn init(self: *Self, x: i32) void {
@@ -154,21 +154,21 @@
}
```
!!! question "尾点指向什么?"
!!! question "尾点指向什么?"
我们将链表的最后一个点称为「尾点」,其指向的是“空”,在 Java, C++, Python 中分别记为 `null`, `nullptr`, `None` 。在不引起歧义的前提下,本书都使用 `null` 来表示空。
我们将链表的最后一个点称为「尾点」,其指向的是“空”,在 Java, C++, Python 中分别记为 `null`, `nullptr`, `None` 。在不引起歧义的前提下,本书都使用 `null` 来表示空。
!!! question "如何称呼链表?"
在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个点对象组成,我们通常将头点当作链表的代称,例如头点 `head` 和链表 `head` 实际上是同义的。
在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个点对象组成,我们通常将头点当作链表的代称,例如头点 `head` 和链表 `head` 实际上是同义的。
**链表初始化方法**。建立链表分为两步,第一步是初始化各个点对象,第二步是构建引用指向关系。完成后,即可以从链表的头点(即首个点)出发,通过指针 `next` 依次访问所有点。
**链表初始化方法**。建立链表分为两步,第一步是初始化各个点对象,第二步是构建引用指向关系。完成后,即可以从链表的头点(即首个点)出发,通过指针 `next` 依次访问所有点。
=== "Java"
```java title="linked_list.java"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
ListNode n0 = new ListNode(1);
ListNode n1 = new ListNode(3);
ListNode n2 = new ListNode(2);
@@ -185,7 +185,7 @@
```cpp title="linked_list.cpp"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
ListNode* n0 = new ListNode(1);
ListNode* n1 = new ListNode(3);
ListNode* n2 = new ListNode(2);
@@ -202,7 +202,7 @@
```python title="linked_list.py"
""" 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """
# 初始化各个
# 初始化各个
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
@@ -219,7 +219,7 @@
```go title="linked_list.go"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
n0 := NewListNode(1)
n1 := NewListNode(3)
n2 := NewListNode(2)
@@ -236,7 +236,7 @@
```javascript title="linked_list.js"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
const n0 = new ListNode(1);
const n1 = new ListNode(3);
const n2 = new ListNode(2);
@@ -253,7 +253,7 @@
```typescript title="linked_list.ts"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
const n0 = new ListNode(1);
const n1 = new ListNode(3);
const n2 = new ListNode(2);
@@ -270,7 +270,7 @@
```c title="linked_list.c"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
ListNode* n0 = newListNode(1);
ListNode* n1 = newListNode(3);
ListNode* n2 = newListNode(2);
@@ -287,7 +287,7 @@
```csharp title="linked_list.cs"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
ListNode n0 = new ListNode(1);
ListNode n1 = new ListNode(3);
ListNode n2 = new ListNode(2);
@@ -304,7 +304,7 @@
```swift title="linked_list.swift"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个
// 初始化各个
let n0 = ListNode(x: 1)
let n1 = ListNode(x: 3)
let n2 = ListNode(x: 2)
@@ -321,7 +321,7 @@
```zig title="linked_list.zig"
// 初始化链表
// 初始化各个
// 初始化各个
var n0 = inc.ListNode(i32){.val = 1};
var n1 = inc.ListNode(i32){.val = 3};
var n2 = inc.ListNode(i32){.val = 2};
@@ -336,9 +336,9 @@
## 链表优点
**链表中插入与删除点的操作效率高**。例如,如果我们想在链表中间的两个点 `A` , `B` 之间插入一个新点 `P` ,我们只需要改变两个点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
**链表中插入与删除点的操作效率高**。例如,如果我们想在链表中间的两个点 `A` , `B` 之间插入一个新点 `P` ,我们只需要改变两个点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
![链表插入点](linked_list.assets/linkedlist_insert_node.png)
![链表插入点](linked_list.assets/linkedlist_insert_node.png)
=== "Java"
@@ -400,9 +400,9 @@
[class]{}-[func]{insert}
```
在链表中删除点也非常方便,只需改变一个点的指针即可。如下图所示,尽管在删除操作完成后,点 `P` 仍然指向 `n1`,但实际上 `P` 已经不再属于此链表,因为遍历此链表时无法访问到 `P`。
在链表中删除点也非常方便,只需改变一个点的指针即可。如下图所示,尽管在删除操作完成后,点 `P` 仍然指向 `n1`,但实际上 `P` 已经不再属于此链表,因为遍历此链表时无法访问到 `P`。
![链表删除点](linked_list.assets/linkedlist_remove_node.png)
![链表删除点](linked_list.assets/linkedlist_remove_node.png)
=== "Java"
@@ -466,7 +466,7 @@
## 链表缺点
**链表访问点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意点,这是因为系统需要从头点出发,逐个向后遍历直至找到目标点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的点,则需要向后遍历 `index` 轮。
**链表访问点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意点,这是因为系统需要从头点出发,逐个向后遍历直至找到目标点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的点,则需要向后遍历 `index` 轮。
=== "Java"
@@ -528,11 +528,11 @@
[class]{}-[func]{access}
```
**链表的内存占用较大**。链表以点为单位,每个点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
**链表的内存占用较大**。链表以点为单位,每个点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
## 链表常用操作
**遍历链表查找**。遍历链表,查找链表内值为 `target` 的点,输出点在链表中的索引。
**遍历链表查找**。遍历链表,查找链表内值为 `target` 的点,输出点在链表中的索引。
=== "Java"
@@ -596,20 +596,20 @@
## 常见链表类型
**单向链表**。即上述介绍的普通链表。单向链表的点包含值和指向下一点的指针(引用)两项数据。我们将首个点称为头点,将最后一个点成为尾点,尾点指向 `null` 。
**单向链表**。即上述介绍的普通链表。单向链表的点包含值和指向下一点的指针(引用)两项数据。我们将首个点称为头点,将最后一个点成为尾点,尾点指向 `null` 。
**环形链表**。如果我们令单向链表的尾点指向头点(即首尾相接),则得到一个环形链表。在环形链表中,任意点都可以视作头点。
**环形链表**。如果我们令单向链表的尾点指向头点(即首尾相接),则得到一个环形链表。在环形链表中,任意点都可以视作头点。
**双向链表**。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的点定义同时包含指向后继点(下一点)和前驱点(上一点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。
**双向链表**。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的点定义同时包含指向后继点(下一点)和前驱点(上一点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。
=== "Java"
```java title=""
/* 双向链表点类 */
/* 双向链表点类 */
class ListNode {
int val; // 点值
ListNode next; // 指向后继点的指针(引用)
ListNode prev; // 指向前驱点的指针(引用)
int val; // 点值
ListNode next; // 指向后继点的指针(引用)
ListNode prev; // 指向前驱点的指针(引用)
ListNode(int x) { val = x; } // 构造函数
}
```
@@ -617,11 +617,11 @@
=== "C++"
```cpp title=""
/* 双向链表点结构体 */
/* 双向链表点结构体 */
struct ListNode {
int val; // 点值
ListNode *next; // 指向后继点的指针(引用)
ListNode *prev; // 指向前驱点的指针(引用)
int val; // 点值
ListNode *next; // 指向后继点的指针(引用)
ListNode *prev; // 指向前驱点的指针(引用)
ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数
};
```
@@ -629,22 +629,22 @@
=== "Python"
```python title=""
""" 双向链表点类 """
""" 双向链表点类 """
class ListNode:
def __init__(self, val: int):
self.val: int = val # 点值
self.next: Optional[ListNode] = None # 指向后继点的指针(引用)
self.prev: Optional[ListNode] = None # 指向前驱点的指针(引用)
self.val: int = val # 点值
self.next: Optional[ListNode] = None # 指向后继点的指针(引用)
self.prev: Optional[ListNode] = None # 指向前驱点的指针(引用)
```
=== "Go"
```go title=""
/* 双向链表点结构体 */
/* 双向链表点结构体 */
type DoublyListNode struct {
Val int // 点值
Next *DoublyListNode // 指向后继点的指针(引用)
Prev *DoublyListNode // 指向前驱点的指针(引用)
Val int // 点值
Next *DoublyListNode // 指向后继点的指针(引用)
Prev *DoublyListNode // 指向前驱点的指针(引用)
}
// NewDoublyListNode 初始化
@@ -660,15 +660,15 @@
=== "JavaScript"
```javascript title=""
/* 双向链表点类 */
/* 双向链表点类 */
class ListNode {
val;
next;
prev;
constructor(val, next) {
this.val = val === undefined ? 0 : val; // 点值
this.next = next === undefined ? null : next; // 指向后继点的指针(引用)
this.prev = prev === undefined ? null : prev; // 指向前驱点的指针(引用)
this.val = val === undefined ? 0 : val; // 点值
this.next = next === undefined ? null : next; // 指向后继点的指针(引用)
this.prev = prev === undefined ? null : prev; // 指向前驱点的指针(引用)
}
}
```
@@ -676,15 +676,15 @@
=== "TypeScript"
```typescript title=""
/* 双向链表点类 */
/* 双向链表点类 */
class ListNode {
val: number;
next: ListNode | null;
prev: ListNode | null;
constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {
this.val = val === undefined ? 0 : val; // 点值
this.next = next === undefined ? null : next; // 指向后继点的指针(引用)
this.prev = prev === undefined ? null : prev; // 指向前驱点的指针(引用)
this.val = val === undefined ? 0 : val; // 点值
this.next = next === undefined ? null : next; // 指向后继点的指针(引用)
this.prev = prev === undefined ? null : prev; // 指向前驱点的指针(引用)
}
}
```
@@ -698,11 +698,11 @@
=== "C#"
```csharp title=""
/* 双向链表点类 */
/* 双向链表点类 */
class ListNode {
int val; // 点值
ListNode next; // 指向后继点的指针(引用)
ListNode prev; // 指向前驱点的指针(引用)
int val; // 点值
ListNode next; // 指向后继点的指针(引用)
ListNode prev; // 指向前驱点的指针(引用)
ListNode(int x) => val = x; // 构造函数
}
```
@@ -710,11 +710,11 @@
=== "Swift"
```swift title=""
/* 双向链表点类 */
/* 双向链表点类 */
class ListNode {
var val: Int // 点值
var next: ListNode? // 指向后继点的指针(引用)
var prev: ListNode? // 指向前驱点的指针(引用)
var val: Int // 点值
var next: ListNode? // 指向后继点的指针(引用)
var prev: ListNode? // 指向前驱点的指针(引用)
init(x: Int) { // 构造函数
val = x
@@ -725,14 +725,14 @@
=== "Zig"
```zig title=""
// 双向链表点类
// 双向链表点类
pub fn ListNode(comptime T: type) type {
return struct {
const Self = @This();
val: T = 0, // 点值
next: ?*Self = null, // 指向后继点的指针(引用)
prev: ?*Self = null, // 指向前驱点的指针(引用)
val: T = 0, // 点值
next: ?*Self = null, // 指向后继点的指针(引用)
prev: ?*Self = null, // 指向前驱点的指针(引用)
// 构造函数
pub fn init(self: *Self, x: i32) void {

View File

@@ -2,7 +2,7 @@
- 数组和链表是两种基本数据结构,分别代表数据在计算机内存中的连续空间存储和离散空间存储方式。两者的优缺点呈现出互补的特性。
- 数组支持随机访问、占用内存较少;但插入和删除元素效率低,且初始化后长度不可变。
- 链表通过更改指针实现高效的点插入与删除,且可以灵活调整长度;但点访问效率低、占用内存较多。常见的链表类型包括单向链表、循环链表、双向链表。
- 链表通过更改指针实现高效的点插入与删除,且可以灵活调整长度;但点访问效率低、占用内存较多。常见的链表类型包括单向链表、循环链表、双向链表。
- 动态数组,又称列表,是基于数组实现的一种数据结构。它保留了数组的优势,同时可以灵活调整长度。列表的出现极大地提高了数组的易用性,但可能导致部分内存空间浪费。
- 下表总结并对比了数组与链表的各项特性。
@@ -19,7 +19,7 @@
!!! note "缓存局部性"
在计算机中,数据读写速度排序是“硬盘 < 内存 < CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其它数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个点,这样的多次“搬运”降低了整体效率。
在计算机中,数据读写速度排序是“硬盘 < 内存 < CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其它数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个点,这样的多次“搬运”降低了整体效率。
- 下表对比了数组与链表在各种操作上的效率。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -78,8 +78,8 @@
""" 类 """
class Node:
def __init__(self, x: int):
self.val: int = x # 点值
self.next: Optional[Node] = None # 指向下一点的指针(引用)
self.val: int = x # 点值
self.next: Optional[Node] = None # 指向下一点的指针(引用)
""" 函数 """
def function() -> int:
@@ -131,8 +131,8 @@
val;
next;
constructor(val) {
this.val = val === undefined ? 0 : val; // 点值
this.next = null; // 指向下一点的引用
this.val = val === undefined ? 0 : val; // 点值
this.next = null; // 指向下一点的引用
}
}
@@ -159,8 +159,8 @@
val: number;
next: Node | null;
constructor(val?: number) {
this.val = val === undefined ? 0 : val; // 点值
this.next = null; // 指向下一点的引用
this.val = val === undefined ? 0 : val; // 点值
this.next = null; // 指向下一点的引用
}
}
@@ -890,7 +890,7 @@ $$
### 指数阶 $O(2^n)$
指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间。
指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间。
=== "Java"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -4,7 +4,7 @@
## 逻辑结构:线性与非线性
**「逻辑结构」揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由点和边构成,反映了复杂的网络关系。
**「逻辑结构」揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由点和边构成,反映了复杂的网络关系。
逻辑结构通常分为「线性」和「非线性」两类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列,例如网状或树状结构。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -12,7 +12,7 @@ $$
![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png)
那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作点,把「边」看作连接各个点的指针,则可将「图」看作是一种从「链表」拓展而来的数据结构。**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,从而更为复杂**。
那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作点,把「边」看作连接各个点的指针,则可将「图」看作是一种从「链表」拓展而来的数据结构。**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,从而更为复杂**。
## 图常见类型
@@ -62,7 +62,7 @@ $$
### 邻接表
「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。
「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。
![图的邻接表表示](graph.assets/adjacency_list.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -94,7 +94,7 @@
- **添加边**:在顶点对应链表的末尾添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。
- **删除边**:在顶点对应链表中查找并删除指定边,使用 $O(m)$ 时间。在无向图中,需要同时删除两个方向的边。
- **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头点,使用 $O(1)$ 时间。
- **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头点,使用 $O(1)$ 时间。
- **删除顶点**:需遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(1)$ 时间。
- **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。
@@ -113,7 +113,7 @@
=== "删除顶点"
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_remove_vertex.png)
以下是基于邻接表实现图的代码示例。细心的同学可能注意到,**我们在邻接表中使用 `Vertex` 点类来表示顶点**,这样做的原因有:
以下是基于邻接表实现图的代码示例。细心的同学可能注意到,**我们在邻接表中使用 `Vertex` 点类来表示顶点**,这样做的原因有:
- 如果我们选择通过顶点值来区分不同顶点,那么值重复的顶点将无法被区分。
- 如果类似邻接矩阵那样,使用顶点列表索引来区分不同顶点。那么,假设我们想要删除索引为 $i$ 的顶点,则需要遍历整个邻接表,将其中 $> i$ 的索引全部减 $1$,这样操作效率较低。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -22,7 +22,7 @@ BFS 通常借助「队列」来实现。队列具有“先入先出”的性质
2. 在循环的每轮迭代中,弹出队首顶点并记录访问,然后将该顶点的所有邻接顶点加入到队列尾部;
3. 循环步骤 `2.` ,直到所有顶点被访问完成后结束;
为了防止重复遍历顶点,我们需要借助一个哈希表 `visited` 来记录哪些点已被访问。
为了防止重复遍历顶点,我们需要借助一个哈希表 `visited` 来记录哪些点已被访问。
=== "Java"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -24,13 +24,13 @@
链式地址下,哈希表的操作方法包括:
- **查询元素**:输入 key ,经过哈希函数得到数组索引,即可访问链表头点,然后遍历链表并对比 key 以查找目标键值对。
- **添加元素**:先通过哈希函数访问链表头点,然后将点(即键值对)添加到链表中。
- **删除元素**:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标点,并将其删除。
- **查询元素**:输入 key ,经过哈希函数得到数组索引,即可访问链表头点,然后遍历链表并对比 key 以查找目标键值对。
- **添加元素**:先通过哈希函数访问链表头点,然后将点(即键值对)添加到链表中。
- **删除元素**:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标点,并将其删除。
尽管链式地址法解决了哈希冲突问题,但仍存在一些局限性,包括:
- **占用空间增大**,由于链表或二叉树包含点指针,相比数组更加耗费内存空间;
- **占用空间增大**,由于链表或二叉树包含点指针,相比数组更加耗费内存空间;
- **查询效率降低**,因为需要线性遍历链表来查找对应元素;
为了提高操作效率,**可以将链表转换为「AVL 树」或「红黑树」**,将查询操作的时间复杂度优化至 $O(\log n)$ 。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -12,8 +12,8 @@
1. **无序数组**:每个元素为 `[学号, 姓名]`
2. **有序数组**:将 `1.` 中的数组按照学号从小到大排序;
3. **链表**:每个点的值为 `[学号, 姓名]`
4. **二叉搜索树**:每个点的值为 `[学号, 姓名]` ,根据学号大小来构建树;
3. **链表**:每个点的值为 `[学号, 姓名]`
4. **二叉搜索树**:每个点的值为 `[学号, 姓名]` ,根据学号大小来构建树;
各项操作的时间复杂度如下表所示(详解可见[二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/))。无论是查找元素还是增删元素,哈希表的时间复杂度都是 $O(1)$,全面胜出!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -1,18 +1,18 @@
# 建堆操作 *
如果我们想要根据输入列表生成一个堆,这样的操作被称为「建堆」。
如果我们想要根据输入列表生成一个堆,这个过程被称为「建堆」。
## 两种建堆方法
### 借助入堆方法实现
最直接地,考虑借助元素入堆」方法,先建立一个空堆,**再将列表元素依次入堆即可**
最直接的方法是借助元素入堆操作”实现,首先创建一个空堆,然后将列表元素依次添加到堆中
设元素数量为 $n$ ,则最后一个元素入堆的时间复杂度为 $O(\log n)$ 在依次入堆时,堆的平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
设元素数量为 $n$ ,则最后一个元素入堆的时间复杂度为 $O(\log n)$ 在依次添加元素时,堆的平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
### 基于堆化操作实现
有趣的是,存在一种更高效的建堆方法,时间复杂度可以达到 $O(n)$ 。我们先将列表所有元素原封不动添加进堆**然后迭代地对各个点执行从顶至底堆化**。当然,**无需对叶点执行堆化**,因为没有子点。
有趣的是,存在一种更高效的建堆方法,时间复杂度仅为 $O(n)$ 。我们先将列表所有元素原封不动添加到堆中**然后迭代地对各个点执行从顶至底堆化**。当然,**我们不需要对叶点执行堆化操作**,因为它们没有子点。
=== "Java"
@@ -76,24 +76,24 @@
## 复杂度分析
第二种建堆方法的时间复杂度为什么是 $O(n)$ ?我们来展开推算一下。
为什么第二种建堆方法的时间复杂度是 $O(n)$ ?我们来展开推算一下。
- 完全二叉树中,设点总数为 $n$ ,则叶点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶点后,需要堆化点数量为 $(n - 1)/2$ 为 $O(n)$
- 从顶至底堆化中,每个点最多堆化至叶结点,因此最大迭代次数为二叉树高度 $O(\log n)$
- 完全二叉树中,设点总数为 $n$ ,则叶点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶点后,需要堆化的节点数量为 $(n - 1)/2$ 复杂度为 $O(n)$
- 从顶至底堆化的过程中,每个点最多堆化到叶节点,因此最大迭代次数为二叉树高度 $O(\log n)$
将上述两者相乘,可得时间复杂度为 $O(n \log n)$ 。这个估算结果不准确,因为我们没有考虑到 **二叉树底层结点远多于顶层结点** 的性质
将上述两者相乘,可得到建堆过程的时间复杂度为 $O(n \log n)$。**然而,这个估算结果不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的特性**
下面我们来展开计算。为了减小计算难度,我们假设树是一个完美二叉树,该假设不会影响计算结果的正确性。设二叉树(即堆)点数量为 $n$ ,树高度为 $h$ 。上文提到,**点堆化最大迭代次数等于该点到叶点的距离,而正是“点高度”**。
接下来我们来进行更为详细的计算。为了减小计算难度,我们假设树是一个完美二叉树,该假设不会影响计算结果的正确性。设二叉树(即堆)点数量为 $n$ ,树高度为 $h$ 。上文提到,**点堆化最大迭代次数等于该点到叶点的距离,而该距离正是“点高度”**。
![完美二叉树的各层点数量](build_heap.assets/heapify_operations_count.png)
![完美二叉树的各层点数量](build_heap.assets/heapify_operations_count.png)
因此,我们将各层的“点数量 $\times$ 点高度”求和,即可得到 **所有点的堆化迭代次数总和**
因此,我们可以将各层的“点数量 $\times$ 点高度”求和,**从而得到所有点的堆化迭代次数总和**。
$$
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
$$
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,得
$$
\begin{aligned}
@@ -108,7 +108,7 @@ $$
2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \cdots + 2^{h-1} + 2^h
$$
观察上式,$T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
观察上式,发现 $T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
$$
\begin{aligned}
@@ -118,4 +118,4 @@ T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline
\end{aligned}
$$
进一步地,高度为 $h$ 的完美二叉树的点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。
进一步地,高度为 $h$ 的完美二叉树的点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Some files were not shown because too many files have changed in this diff Show More