This commit is contained in:
krahets
2024-04-28 22:35:59 +08:00
parent f986ae3c8c
commit f748af6aa4
34 changed files with 588 additions and 136 deletions

View File

@@ -56,6 +56,7 @@ comments: true
| front of the queue | 队首 | 佇列首 |
| rear of the queue | 队尾 | 佇列尾 |
| hash table | 哈希表 | 雜湊表 |
| hash set | 哈希集合 | 雜湊集合 |
| bucket | 桶 | 桶 |
| hash function | 哈希函数 | 雜湊函式 |
| hash collision | 哈希冲突 | 雜湊衝突 |

View File

@@ -1555,7 +1555,7 @@ comments: true
单向链表通常用于实现栈、队列、哈希表和图等数据结构。
- **栈与队列**:当插入和删除操作都在链表的一端进行时,它表现先进后出的特性,对应栈;当插入操作在链表的一端进行,删除操作在链表的另一端进行,它表现先进先出的特性,对应队列。
- **栈与队列**:当插入和删除操作都在链表的一端进行时,它表现的特性为先进后出,对应栈;当插入操作在链表的一端进行,删除操作在链表的另一端进行,它表现的特性为先进先出,对应队列。
- **哈希表**:链式地址是解决哈希冲突的主流方案之一,在该方案中,所有冲突的元素都会被放到一个链表中。
- **图**:邻接表是表示图的一种常用方式,其中图的每个顶点都与一个链表相关联,链表中的每个元素都代表与该顶点相连的其他顶点。

View File

@@ -36,7 +36,7 @@ status: new
<p align="center"> 图 4-9 &nbsp; 计算机存储系统 </p>
!!! note
!!! tip
计算机的存储层次结构体现了速度、容量和成本三者之间的精妙平衡。实际上,这种权衡普遍存在于所有工业领域,它要求我们在不同的优势和限制之间找到最佳平衡点。

View File

@@ -538,7 +538,7 @@ comments: true
<p align="center"> 图 13-7 &nbsp; 重复排列 </p>
那么如何去除重复的排列呢?最直接地,考虑借助一个哈希,直接对排列结果进行去重。然而这样做不够优雅,**因为生成重复排列的搜索分支没有必要,应当提前识别并剪枝**,这样可以进一步提升算法效率。
那么如何去除重复的排列呢?最直接地,考虑借助一个哈希集合,直接对排列结果进行去重。然而这样做不够优雅,**因为生成重复排列的搜索分支没有必要,应当提前识别并剪枝**,这样可以进一步提升算法效率。
### 1. &nbsp; 相等元素剪枝
@@ -554,7 +554,7 @@ comments: true
### 2. &nbsp; 代码实现
在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希 `duplicated` ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝:
在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希集合 `duplicated` ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝:
=== "Python"

View File

@@ -11,7 +11,7 @@ comments: true
- 回溯问题通常包含多个约束条件,它们可用于实现剪枝操作。剪枝可以提前结束不必要的搜索分支,大幅提升搜索效率。
- 回溯算法主要可用于解决搜索问题和约束满足问题。组合优化问题虽然可以用回溯算法解决,但往往存在效率更高或效果更好的解法。
- 全排列问题旨在搜索给定集合元素的所有可能的排列。我们借助一个数组来记录每个元素是否被选择,剪掉重复选择同一元素的搜索分支,确保每个元素只被选择一次。
- 在全排列问题中,如果集合中存在重复元素,则最终结果会出现重复排列。我们需要约束相等元素在每轮中只能被选择一次,这通常借助一个哈希来实现。
- 在全排列问题中,如果集合中存在重复元素,则最终结果会出现重复排列。我们需要约束相等元素在每轮中只能被选择一次,这通常借助一个哈希集合来实现。
- 子集和问题的目标是在给定集合中找到和为目标值的所有子集。集合不区分元素顺序,而搜索过程会输出所有顺序的结果,产生重复子集。我们在回溯前将数据进行排序,并设置一个变量来指示每一轮的遍历起始点,从而将生成重复子集的搜索分支进行剪枝。
- 对于子集和问题,数组中的相等元素会产生重复集合。我们利用数组已排序的前置条件,通过判断相邻元素是否相等实现剪枝,从而确保相等元素在每轮中只能被选中一次。
- $n$ 皇后问题旨在寻找将 $n$ 个皇后放置到 $n \times n$ 尺寸棋盘上的方案,要求所有皇后两两之间无法攻击对方。该问题的约束条件有行约束、列约束、主对角线和次对角线约束。为满足行约束,我们采用按行放置的策略,保证每一行放置一个皇后。

View File

@@ -753,7 +753,7 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因
时间复杂度分析本质上是计算“操作数量 $T(n)$”的渐近上界,它具有明确的数学定义。
!!! abstract "函数渐近上界"
!!! note "函数渐近上界"
若存在正实数 $c$ 和实数 $n_0$ ,使得对于所有的 $n > n_0$ ,均有 $T(n) \leq c \cdot f(n)$ ,则可认为 $f(n)$ 给出了 $T(n)$ 的一个渐近上界,记为 $T(n) = O(f(n))$ 。

View File

@@ -4,7 +4,7 @@ comments: true
# 3.3 &nbsp; 数字编码 *
!!! note
!!! tip
在本书中,标题带有 * 符号的是选读章节。如果你时间有限或感到理解困难,可以先跳过,等学完必读章节后再单独攻克。

View File

@@ -24,7 +24,11 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
2. 在循环的每轮迭代中,弹出队首顶点并记录访问,然后将该顶点的所有邻接顶点加入到队列尾部。
3. 循环步骤 `2.` ,直到所有顶点被访问完毕后结束。
为了防止重复遍历顶点,我们需要借助一个哈希 `visited` 来记录哪些节点已被访问。
为了防止重复遍历顶点,我们需要借助一个哈希集合 `visited` 来记录哪些节点已被访问。
!!! tip
哈希集合可以看作一个只存储 `key` 而不存储 `value` 的哈希表,它可以在 $O(1)$ 时间复杂度下进行 `key` 的增删查改操作。根据 `key` 的唯一性,哈希集合通常用于数据去重等场景。
=== "Python"
@@ -34,7 +38,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = set[Vertex]([start_vet])
# 队列用于实现 BFS
que = deque[Vertex]([start_vet])
@@ -60,7 +64,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
vector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {
// 顶点遍历序列
vector<Vertex *> res;
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
unordered_set<Vertex *> visited = {startVet};
// 队列用于实现 BFS
queue<Vertex *> que;
@@ -91,7 +95,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
List<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = new ArrayList<>();
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
Set<Vertex> visited = new HashSet<>();
visited.add(startVet);
// 队列用于实现 BFS
@@ -122,7 +126,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
List<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
HashSet<Vertex> visited = [startVet];
// 队列用于实现 BFS
Queue<Vertex> que = new();
@@ -153,7 +157,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
func graphBFS(g *graphAdjList, startVet Vertex) []Vertex {
// 顶点遍历序列
res := make([]Vertex, 0)
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
visited := make(map[Vertex]struct{})
visited[startVet] = struct{}{}
// 队列用于实现 BFS, 使用切片模拟队列
@@ -189,7 +193,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {
// 顶点遍历序列
var res: [Vertex] = []
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
var visited: Set<Vertex> = [startVet]
// 队列用于实现 BFS
var que: [Vertex] = [startVet]
@@ -219,7 +223,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
function graphBFS(graph, startVet) {
// 顶点遍历序列
const res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
const visited = new Set();
visited.add(startVet);
// 队列用于实现 BFS
@@ -250,7 +254,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {
// 顶点遍历序列
const res: Vertex[] = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
const visited: Set<Vertex> = new Set();
visited.add(startVet);
// 队列用于实现 BFS
@@ -281,7 +285,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
Set<Vertex> visited = {};
visited.add(startVet);
// 队列用于实现 BFS
@@ -313,7 +317,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {
// 顶点遍历序列
let mut res = vec![];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
let mut visited = HashSet::new();
visited.insert(start_vet);
// 队列用于实现 BFS
@@ -421,7 +425,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {
// 顶点遍历序列
val res = mutableListOf<Vertex?>()
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
val visited = HashSet<Vertex>()
visited.add(startVet)
// 队列用于实现 BFS
@@ -452,7 +456,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = Set.new([start_vet])
# 队列用于实现 BFS
que = [start_vet]
@@ -528,7 +532,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
**时间复杂度**:所有顶点都会入队并出队一次,使用 $O(|V|)$ 时间;在遍历邻接顶点的过程中,由于是无向图,因此所有边都会被访问 $2$ 次,使用 $O(2|E|)$ 时间;总体使用 $O(|V| + |E|)$ 时间。
**空间复杂度**:列表 `res` ,哈希 `visited` ,队列 `que` 中的顶点数量最多为 $|V|$ ,使用 $O(|V|)$ 空间。
**空间复杂度**:列表 `res` ,哈希集合 `visited` ,队列 `que` 中的顶点数量最多为 $|V|$ ,使用 $O(|V|)$ 空间。
## 9.3.2 &nbsp; 深度优先遍历
@@ -540,7 +544,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
### 1. &nbsp; 算法实现
这种“走到尽头再返回”的算法范式通常基于递归来实现。与广度优先遍历类似,在深度优先遍历中,我们也需要借助一个哈希 `visited` 来记录已被访问的顶点,以避免重复访问顶点。
这种“走到尽头再返回”的算法范式通常基于递归来实现。与广度优先遍历类似,在深度优先遍历中,我们也需要借助一个哈希集合 `visited` 来记录已被访问的顶点,以避免重复访问顶点。
=== "Python"
@@ -561,7 +565,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = set[Vertex]()
dfs(graph, visited, res, start_vet)
return res
@@ -588,7 +592,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
vector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {
// 顶点遍历序列
vector<Vertex *> res;
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
unordered_set<Vertex *> visited;
dfs(graph, visited, res, startVet);
return res;
@@ -616,7 +620,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
List<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = new ArrayList<>();
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
Set<Vertex> visited = new HashSet<>();
dfs(graph, visited, res, startVet);
return res;
@@ -645,7 +649,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
List<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
HashSet<Vertex> visited = [];
DFS(graph, visited, res, startVet);
return res;
@@ -675,7 +679,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
func graphDFS(g *graphAdjList, startVet Vertex) []Vertex {
// 顶点遍历序列
res := make([]Vertex, 0)
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
visited := make(map[Vertex]struct{})
dfs(g, visited, &res, startVet)
// 返回顶点遍历序列
@@ -705,7 +709,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {
// 顶点遍历序列
var res: [Vertex] = []
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
var visited: Set<Vertex> = []
dfs(graph: graph, visited: &visited, res: &res, vet: startVet)
return res
@@ -735,7 +739,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
function graphDFS(graph, startVet) {
// 顶点遍历序列
const res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
const visited = new Set();
dfs(graph, visited, res, startVet);
return res;
@@ -769,7 +773,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {
// 顶点遍历序列
const res: Vertex[] = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
const visited: Set<Vertex> = new Set();
dfs(graph, visited, res, startVet);
return res;
@@ -802,7 +806,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
List<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
Set<Vertex> visited = {};
dfs(graph, visited, res, startVet);
return res;
@@ -833,7 +837,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {
// 顶点遍历序列
let mut res = vec![];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
let mut visited = HashSet::new();
dfs(&graph, &mut visited, &mut res, start_vet);
@@ -904,7 +908,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList<Vertex?> {
// 顶点遍历序列
val res = mutableListOf<Vertex?>()
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
val visited = HashSet<Vertex?>()
dfs(graph, visited, res, startVet)
return res
@@ -931,7 +935,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = Set.new
dfs(graph, visited, res, start_vet)
res
@@ -1003,4 +1007,4 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
**时间复杂度**:所有顶点都会被访问 $1$ 次,使用 $O(|V|)$ 时间;所有边都会被访问 $2$ 次,使用 $O(2|E|)$ 时间;总体使用 $O(|V| + |E|)$ 时间。
**空间复杂度**:列表 `res` ,哈希 `visited` 顶点数量最多为 $|V|$ ,递归深度最大为 $|V|$ ,因此使用 $O(|V|)$ 空间。
**空间复杂度**:列表 `res` ,哈希集合 `visited` 顶点数量最多为 $|V|$ ,递归深度最大为 $|V|$ ,因此使用 $O(|V|)$ 空间。

View File

@@ -676,11 +676,20 @@ comments: true
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{left}
### 获取左子节点的索引 ###
def left(i)
2 * i + 1
end
[class]{MaxHeap}-[func]{right}
### 获取右子节点的索引 ###
def right(i)
2 * i + 2
end
[class]{MaxHeap}-[func]{parent}
### 获取父节点的索引 ###
def parent(i)
(i - 1) / 2 # 向下整除
end
```
=== "Zig"
@@ -817,7 +826,10 @@ comments: true
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{peek}
### 访问堆顶元素 ###
def peek
@max_heap[0]
end
```
=== "Zig"
@@ -1211,9 +1223,27 @@ comments: true
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{push}
### 元素入堆 ###
def push(val)
# 添加节点
@max_heap << val
# 从底至顶堆化
sift_up(size - 1)
end
[class]{MaxHeap}-[func]{sift_up}
### 从节点 i 开始,从底至顶堆化 ###
def sift_up(i)
loop do
# 获取节点 i 的父节点
p = parent(i)
# 当“越过根节点”或“节点无须修复”时,结束堆化
break if p < 0 || @max_heap[i] <= @max_heap[p]
# 交换两节点
swap(i, p)
# 循环向上堆化
i = p
end
end
```
=== "Zig"
@@ -1649,7 +1679,7 @@ comments: true
// 交换根节点与最右叶节点(交换首元素与尾元素)
self.swap(0, self.size() - 1);
// 删除节点
let val = self.max_heap.remove(self.size() - 1);
let val = self.max_heap.pop().unwrap();
// 从顶至底堆化
self.sift_down(0);
// 返回堆顶元素
@@ -1767,9 +1797,37 @@ comments: true
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{pop}
### 元素出堆 ###
def pop
# 判空处理
raise IndexError, "堆为空" if is_empty?
# 交换根节点与最右叶节点(交换首元素与尾元素)
swap(0, size - 1)
# 删除节点
val = @max_heap.pop
# 从顶至底堆化
sift_down(0)
# 返回堆顶元素
val
end
[class]{MaxHeap}-[func]{sift_down}
### 从节点 i 开始,从顶至底堆化 ###
def sift_down(i)
loop do
# 判断节点 i, l, r 中值最大的节点,记为 ma
l, r, ma = left(i), right(i), i
ma = l if l < size && @max_heap[l] > @max_heap[ma]
ma = r if r < size && @max_heap[r] > @max_heap[ma]
# 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
break if ma == i
# 交换两节点
swap(i, ma)
# 循环向下堆化
i = ma
end
end
```
=== "Zig"

View File

@@ -437,7 +437,28 @@ comments: true
=== "Ruby"
```ruby title="top_k.rb"
[class]{}-[func]{top_k_heap}
### 基于堆查找数组中最大的 k 个元素 ###
def top_k_heap(nums, k)
# 初始化小顶堆
# 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆
max_heap = MaxHeap.new([])
# 将数组的前 k 个元素入堆
for i in 0...k
push_min_heap(max_heap, nums[i])
end
# 从第 k+1 个元素开始,保持堆的长度为 k
for i in k...nums.length
# 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆
if nums[i] > peek_min_heap(max_heap)
pop_min_heap(max_heap)
push_min_heap(max_heap, nums[i])
end
end
get_min_heap(max_heap)
end
```
=== "Zig"

View File

@@ -11,3 +11,16 @@ comments: true
- 算法是在有限时间内解决特定问题的一组指令或操作步骤,而数据结构是计算机中组织和存储数据的方式。
- 数据结构与算法紧密相连。数据结构是算法的基石,而算法是数据结构发挥作用的舞台。
- 我们可以将数据结构与算法类比为拼装积木,积木代表数据,积木的形状和连接方式等代表数据结构,拼装积木的步骤则对应算法。
### 1. &nbsp; Q & A
**Q**:作为一名程序员,我在日常工作中从未用算法解决过问题,常用算法都被编程语言封装好了,直接用就可以了;这是否意味着我们工作中的问题还没有到达需要算法的程度?
如果把具体的工作技能比作是武功的“招式”的话,那么基础科目应该更像是“内功”。
我认为学算法(以及其他基础科目)的意义不是在于在工作中从零实现它,而是基于学到的知识,在解决问题时能够作出专业的反应和判断,从而提升工作的整体质量。举一个简单例子,每种编程语言都内置了排序函数:
- 如果我们没有学过数据结构与算法,那么给定任何数据,我们可能都塞给这个排序函数去做了。运行顺畅、性能不错,看上去并没有什么问题。
- 但如果学过算法,我们就会知道内置排序函数的时间复杂度是 $O(n \log n)$ ;而如果给定的数据是固定位数的整数(例如学号),那么我们就可以用效率更高的“基数排序”来做,将时间复杂度降为 $O(nk)$ ,其中 $k$ 为位数。当数据体量很大时,节省出来的运行时间就能创造较大价值(成本降低、体验变好等)。
在工程领域中,大量问题是难以达到最优解的,许多问题只是被“差不多”地解决了。问题的难易程度一方面取决于问题本身的性质,另一方面也取决于观测问题的人的知识储备。人的知识越完备、经验越多,分析问题就会越深入,问题就能被解决得更优雅。

View File

@@ -681,7 +681,7 @@ AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二
}
```
!!! note
!!! tip
设平衡因子为 $f$ ,则一棵 AVL 树的任意节点的平衡因子皆满足 $-1 \le f \le 1$ 。

View File

@@ -645,7 +645,7 @@ comments: true
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=37&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=37&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全屏观看 ></a></div>
!!! note
!!! tip
需要注意的是,插入节点可能会改变二叉树的原有逻辑结构,而删除节点通常意味着删除该节点及其所有子树。因此,在二叉树中,插入与删除通常是由一套操作配合完成的,以实现有实际意义的操作。