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

@ -35,7 +35,7 @@ We can imagine the computer storage system as a pyramid structure shown in the F
<p align="center"> Figure 4-9 &nbsp; Computer storage system </p>
!!! note
!!! tip
The storage hierarchy of computers reflects a delicate balance between speed, capacity, and cost. In fact, this kind of trade-off is common in all industrial fields, requiring us to find the best balance between different advantages and limitations.

View File

@ -671,7 +671,7 @@ Since $T(n)$ is a linear function, its growth trend is linear, and therefore, it
In essence, time complexity analysis is about finding the asymptotic upper bound of the "number of operations $T(n)$". It has a precise mathematical definition.
!!! abstract "Asymptotic Upper Bound"
!!! note "Asymptotic Upper Bound"
If there exist positive real numbers $c$ and $n_0$ such that for all $n > n_0$, $T(n) \leq c \cdot f(n)$, then $f(n)$ is considered an asymptotic upper bound of $T(n)$, denoted as $T(n) = O(f(n))$.

View File

@ -4,7 +4,7 @@ comments: true
# 3.3 &nbsp; Number encoding *
!!! note
!!! tip
In this book, chapters marked with an asterisk '*' are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters.

View File

@ -34,7 +34,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = set[Vertex]([start_vet])
# 队列用于实现 BFS
que = deque[Vertex]([start_vet])
@ -60,7 +60,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
vector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {
// 顶点遍历序列
vector<Vertex *> res;
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
unordered_set<Vertex *> visited = {startVet};
// 队列用于实现 BFS
queue<Vertex *> que;
@ -91,7 +91,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
List<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = new ArrayList<>();
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
Set<Vertex> visited = new HashSet<>();
visited.add(startVet);
// 队列用于实现 BFS
@ -122,7 +122,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
List<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
HashSet<Vertex> visited = [startVet];
// 队列用于实现 BFS
Queue<Vertex> que = new();
@ -153,7 +153,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
func graphBFS(g *graphAdjList, startVet Vertex) []Vertex {
// 顶点遍历序列
res := make([]Vertex, 0)
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
visited := make(map[Vertex]struct{})
visited[startVet] = struct{}{}
// 队列用于实现 BFS, 使用切片模拟队列
@ -189,7 +189,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {
// 顶点遍历序列
var res: [Vertex] = []
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
var visited: Set<Vertex> = [startVet]
// 队列用于实现 BFS
var que: [Vertex] = [startVet]
@ -219,7 +219,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
function graphBFS(graph, startVet) {
// 顶点遍历序列
const res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
const visited = new Set();
visited.add(startVet);
// 队列用于实现 BFS
@ -250,7 +250,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {
// 顶点遍历序列
const res: Vertex[] = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
const visited: Set<Vertex> = new Set();
visited.add(startVet);
// 队列用于实现 BFS
@ -281,7 +281,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
Set<Vertex> visited = {};
visited.add(startVet);
// 队列用于实现 BFS
@ -313,7 +313,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
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 +421,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {
// 顶点遍历序列
val res = mutableListOf<Vertex?>()
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
val visited = HashSet<Vertex>()
visited.add(startVet)
// 队列用于实现 BFS
@ -452,7 +452,7 @@ To prevent revisiting vertices, we use a hash table `visited` to record which no
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = Set.new([start_vet])
# 队列用于实现 BFS
que = [start_vet]
@ -561,7 +561,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = set[Vertex]()
dfs(graph, visited, res, start_vet)
return res
@ -588,7 +588,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
vector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {
// 顶点遍历序列
vector<Vertex *> res;
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
unordered_set<Vertex *> visited;
dfs(graph, visited, res, startVet);
return res;
@ -616,7 +616,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
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 +645,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
List<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
HashSet<Vertex> visited = [];
DFS(graph, visited, res, startVet);
return res;
@ -675,7 +675,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
func graphDFS(g *graphAdjList, startVet Vertex) []Vertex {
// 顶点遍历序列
res := make([]Vertex, 0)
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
visited := make(map[Vertex]struct{})
dfs(g, visited, &res, startVet)
// 返回顶点遍历序列
@ -705,7 +705,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
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 +735,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
function graphDFS(graph, startVet) {
// 顶点遍历序列
const res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
const visited = new Set();
dfs(graph, visited, res, startVet);
return res;
@ -769,7 +769,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
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 +802,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
List<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {
// 顶点遍历序列
List<Vertex> res = [];
// 哈希,用于记录已被访问过的顶点
// 哈希集合,用于记录已被访问过的顶点
Set<Vertex> visited = {};
dfs(graph, visited, res, startVet);
return res;
@ -833,7 +833,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
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 +904,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
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 +931,7 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
# 顶点遍历序列
res = []
# 哈希,用于记录已被访问过的顶点
# 哈希集合,用于记录已被访问过的顶点
visited = Set.new
dfs(graph, visited, res, start_vet)
res

View File

@ -675,11 +675,20 @@ We can encapsulate the index mapping formula into functions for convenient later
=== "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"
@ -816,7 +825,10 @@ The top element of the heap is the root node of the binary tree, which is also t
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{peek}
### 访问堆顶元素 ###
def peek
@max_heap[0]
end
```
=== "Zig"
@ -1210,9 +1222,27 @@ Given a total of $n$ nodes, the height of the tree is $O(\log n)$. Hence, the lo
=== "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"
@ -1648,7 +1678,7 @@ Similar to the element insertion operation, the time complexity of the top eleme
// 交换根节点与最右叶节点(交换首元素与尾元素)
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);
// 返回堆顶元素
@ -1766,9 +1796,37 @@ Similar to the element insertion operation, the time complexity of the top eleme
=== "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 @@ Example code is as follows:
=== "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

@ -670,7 +670,7 @@ The "balance factor" of a node is defined as the height of the node's left subtr
}
```
!!! note
!!! tip
Let the balance factor be $f$, then the balance factor of any node in an AVL tree satisfies $-1 \le f \le 1$.

View File

@ -617,7 +617,7 @@ Similar to a linked list, inserting and removing nodes in a binary tree can be a
https://pythontutor.com/render.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&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
!!! note
!!! tip
It's important to note that inserting nodes may change the original logical structure of the binary tree, while removing nodes usually means removing the node and all its subtrees. Therefore, in a binary tree, insertion and removal are usually performed through a set of operations to achieve meaningful actions.