This commit is contained in:
krahets
2024-05-04 19:57:03 +08:00
parent 6d966b8b5d
commit 2395804410
27 changed files with 792 additions and 91 deletions

View File

@@ -269,6 +269,7 @@ Please note, in an $n$-dimensional matrix, the range of $row - col$ is $[-n + 1,
}
*res = append(*res, newState)
return
}
// 遍历所有列
for col := 0; col < n; col++ {

View File

@@ -735,8 +735,8 @@ Let the size of the input data be $n$, the following chart displays common types
$$
\begin{aligned}
O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
\text{Constant Order} < \text{Logarithmic Order} < \text{Linear Order} < \text{Quadratic Order} < \text{Exponential Order}
& O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
& \text{Constant} < \text{Logarithmic} < \text{Linear} < \text{Quadratic} < \text{Exponential}
\end{aligned}
$$

View File

@@ -967,8 +967,8 @@ Let's consider the input data size as $n$. The common types of time complexities
$$
\begin{aligned}
O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
\text{Constant Order} < \text{Logarithmic Order} < \text{Linear Order} < \text{Linear-Logarithmic Order} < \text{Quadratic Order} < \text{Exponential Order} < \text{Factorial Order}
& O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
& \text{Constant} < \text{Log} < \text{Linear} < \text{Linear-Log} < \text{Quadratic} < \text{Exp} < \text{Factorial}
\end{aligned}
$$

View File

@@ -24,7 +24,7 @@ BFS is usually implemented with the help of a queue, as shown in the code below.
2. In each iteration of the loop, pop the vertex at the front of the queue and record it as visited, then add all adjacent vertices of that vertex to the back of the queue.
3. Repeat step `2.` until all vertices have been visited.
To prevent revisiting vertices, we use a hash table `visited` to record which nodes have been visited.
To prevent revisiting vertices, we use a hash set `visited` to record which nodes have been visited.
=== "Python"
@@ -528,7 +528,7 @@ The code is relatively abstract, it is suggested to compare with Figure 9-10 to
**Time complexity**: All vertices will be enqueued and dequeued once, using $O(|V|)$ time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited $2$ times, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time.
**Space complexity**: The maximum number of vertices in list `res`, hash table `visited`, and queue `que` is $|V|$, using $O(|V|)$ space.
**Space complexity**: The maximum number of vertices in list `res`, hash set `visited`, and queue `que` is $|V|$, using $O(|V|)$ space.
## 9.3.2 &nbsp; Depth-first search
@@ -540,7 +540,7 @@ The code is relatively abstract, it is suggested to compare with Figure 9-10 to
### 1. &nbsp; Algorithm implementation
This "go as far as possible and then return" algorithm paradigm is usually implemented based on recursion. Similar to breadth-first search, in depth-first search, we also need the help of a hash table `visited` to record the visited vertices to avoid revisiting.
This "go as far as possible and then return" algorithm paradigm is usually implemented based on recursion. Similar to breadth-first search, in depth-first search, we also need the help of a hash set `visited` to record the visited vertices to avoid revisiting.
=== "Python"
@@ -1003,4 +1003,4 @@ To deepen the understanding, it is suggested to combine Figure 9-12 with the cod
**Time complexity**: All vertices will be visited once, using $O(|V|)$ time; all edges will be visited twice, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time.
**Space complexity**: The maximum number of vertices in list `res`, hash table `visited` is $|V|$, and the maximum recursion depth is $|V|$, therefore using $O(|V|)$ space.
**Space complexity**: The maximum number of vertices in list `res`, hash set `visited` is $|V|$, and the maximum recursion depth is $|V|$, therefore using $O(|V|)$ space.

File diff suppressed because one or more lines are too long

View File

@@ -276,7 +276,20 @@ Example code is as follows:
=== "Ruby"
```ruby title="bubble_sort.rb"
[class]{}-[func]{bubble_sort}
### 冒泡排序 ###
def bubble_sort(nums)
n = nums.length
# 外循环:未排序区间为 [0, i]
for i in (n - 1).downto(1)
# 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端
for j in 0...i
if nums[j] > nums[j + 1]
# 交换 nums[j] 与 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
end
end
end
end
```
=== "Zig"
@@ -587,7 +600,25 @@ Even after optimization, the worst-case time complexity and average time complex
=== "Ruby"
```ruby title="bubble_sort.rb"
[class]{}-[func]{bubble_sort_with_flag}
### 冒泡排序(标志优化)###
def bubble_sort_with_flag(nums)
n = nums.length
# 外循环:未排序区间为 [0, i]
for i in (n - 1).downto(1)
flag = false # 初始化标志位
# 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端
for j in 0...i
if nums[j] > nums[j + 1]
# 交换 nums[j] 与 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = true # 记录交换元素
end
end
break unless flag # 此轮“冒泡”未交换任何元素,直接跳出
end
end
```
=== "Zig"

View File

@@ -348,7 +348,25 @@ The code is shown below:
=== "Ruby"
```ruby title="counting_sort.rb"
[class]{}-[func]{counting_sort_naive}
### 计数排序 ###
def counting_sort_naive(nums)
# 简单实现,无法用于排序对象
# 1. 统计数组最大元素 m
m = 0
nums.each { |num| m = [m, num].max }
# 2. 统计各数字的出现次数
# counter[num] 代表 num 的出现次数
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. 遍历 counter ,将各元素填入原数组 nums
i = 0
for num in 0...(m + 1)
(0...counter[num]).each do
nums[i] = num
i += 1
end
end
end
```
=== "Zig"
@@ -854,7 +872,30 @@ The implementation code of counting sort is shown below:
=== "Ruby"
```ruby title="counting_sort.rb"
[class]{}-[func]{counting_sort}
### 计数排序 ###
def counting_sort(nums)
# 完整实现,可排序对象,并且是稳定排序
# 1. 统计数组最大元素 m
m = nums.max
# 2. 统计各数字的出现次数
# counter[num] 代表 num 的出现次数
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引”
# 即 counter[num]-1 是 num 在 res 中最后一次出现的索引
(0...m).each { |i| counter[i + 1] += counter[i] }
# 4. 倒序遍历 nums, 将各元素填入结果数组 res
# 初始化数组 res 用于记录结果
n = nums.length
res = Array.new(n, 0)
(n - 1).downto(0).each do |i|
num = nums[i]
res[counter[num] - 1] = num # 将 num 放置到对应索引处
counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引
end
# 使用结果数组 res 覆盖原数组 nums
(0...n).each { |i| nums[i] = res[i] }
end
```
=== "Zig"

View File

@@ -353,7 +353,24 @@ After the pivot partitioning, the original array is divided into three parts: le
=== "Ruby"
```ruby title="quick_sort.rb"
[class]{QuickSort}-[func]{partition}
### 哨兵划分 ###
def partition(nums, left, right)
# 以 nums[left] 为基准数
i, j = left, right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # 从右向左找首个小于基准数的元素
end
while i < j && nums[i] <= nums[left]
i += 1 # 从左向右找首个大于基准数的元素
end
# 元素交换
nums[i], nums[j] = nums[j], nums[i]
end
# 将基准数交换至两子数组的分界线
nums[i], nums[left] = nums[left], nums[i]
i # 返回基准数的索引
end
```
=== "Zig"
@@ -594,7 +611,18 @@ The overall process of quick sort is shown in Figure 11-9.
=== "Ruby"
```ruby title="quick_sort.rb"
[class]{QuickSort}-[func]{quick_sort}
### 快速排序类 ###
def quick_sort(nums, left, right)
# 子数组长度不为 1 时递归
if left < right
# 哨兵划分
pivot = partition(nums, left, right)
# 递归左子数组、右子数组
quick_sort(nums, left, pivot - 1)
quick_sort(nums, pivot + 1, right)
end
nums
end
```
=== "Zig"
@@ -1067,9 +1095,38 @@ Sample code is as follows:
=== "Ruby"
```ruby title="quick_sort.rb"
[class]{QuickSortMedian}-[func]{median_three}
### 选取三个候选元素的中位数 ###
def median_three(nums, left, mid, right)
# 选取三个候选元素的中位数
_l, _m, _r = nums[left], nums[mid], nums[right]
# m 在 l 和 r 之间
return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)
# l 在 m 和 r 之间
return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)
return right
end
[class]{QuickSortMedian}-[func]{partition}
### 哨兵划分(三数取中值)###
def partition(nums, left, right)
### 以 nums[left] 为基准数
med = median_three(nums, left, (left + right) / 2, right)
# 将中位数交换至数组最左断
nums[left], nums[med] = nums[med], nums[left]
i, j = left, right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # 从右向左找首个小于基准数的元素
end
while i < j && nums[i] <= nums[left]
i += 1 # 从左向右找首个大于基准数的元素
end
# 元素交换
nums[i], nums[j] = nums[j], nums[i]
end
# 将基准数交换至两子数组的分界线
nums[i], nums[left] = nums[left], nums[i]
i # 返回基准数的索引
end
```
=== "Zig"
@@ -1377,7 +1434,22 @@ To prevent the accumulation of stack frame space, we can compare the lengths of
=== "Ruby"
```ruby title="quick_sort.rb"
[class]{QuickSortTailCall}-[func]{quick_sort}
### 快速排序(尾递归优化)###
def quick_sort(nums, left, right)
# 子数组长度不为 1 时递归
while left < right
# 哨兵划分
pivot = partition(nums, left, right)
# 对两个子数组中较短的那个执行快速排序
if pivot - left < right - pivot
quick_sort(nums, left, pivot - 1)
left = pivot + 1 # 剩余未排序区间为 [pivot + 1, right]
else
quick_sort(nums, pivot + 1, right)
right = pivot - 1 # 剩余未排序区间为 [left, pivot - 1]
end
end
end
```
=== "Zig"

View File

@@ -677,11 +677,51 @@ Additionally, we need to slightly modify the counting sort code to allow sorting
=== "Ruby"
```ruby title="radix_sort.rb"
[class]{}-[func]{digit}
### 获取元素 num 的第 k 位,其中 exp = 10^(k-1) ###
def digit(num, exp)
# 转入 exp 而非 k 可以避免在此重复执行昂贵的次方计算
(num / exp) % 10
end
[class]{}-[func]{counting_sort_digit}
### 计数排序(根据 nums 第 k 位排序)###
def counting_sort_digit(nums, exp)
# 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组
counter = Array.new(10, 0)
n = nums.length
# 统计 0~9 各数字的出现次数
for i in 0...n
d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d
counter[d] += 1 # 统计数字 d 的出现次数
end
# 求前缀和,将“出现个数”转换为“数组索引”
(1...10).each { |i| counter[i] += counter[i - 1] }
# 倒序遍历,根据桶内统计结果,将各元素填入 res
res = Array.new(n, 0)
for i in (n - 1).downto(0)
d = digit(nums[i], exp)
j = counter[d] - 1 # 获取 d 在数组中的索引 j
res[j] = nums[i] # 将当前元素填入索引 j
counter[d] -= 1 # 将 d 的数量减 1
end
# 使用结果覆盖原数组 nums
(0...n).each { |i| nums[i] = res[i] }
end
[class]{}-[func]{radix_sort}
### 基数排序 ###
def radix_sort(nums)
# 获取数组的最大元素,用于判断最大位数
m = nums.max
# 按照从低位到高位的顺序遍历
exp = 1
while exp <= m
# 对数组元素的第 k 位执行计数排序
# k = 1 -> exp = 1
# k = 2 -> exp = 10
# 即 exp = 10^(k-1)
counting_sort_digit(nums, exp)
exp *= 10
end
end
```
=== "Zig"

View File

@@ -306,7 +306,22 @@ In the code, we use $k$ to record the smallest element within the unsorted inter
=== "Ruby"
```ruby title="selection_sort.rb"
[class]{}-[func]{selection_sort}
### 选择排序 ###
def selection_sort(nums)
n = nums.length
# 外循环:未排序区间为 [i, n-1]
for i in 0...(n - 1)
# 内循环:找到未排序区间内的最小元素
k = i
for j in (i + 1)...n
if nums[j] < nums[k]
k = j # 记录最小元素的索引
end
end
# 将该最小元素与未排序区间的首个元素交换
nums[i], nums[k] = nums[k], nums[i]
end
end
```
=== "Zig"

View File

@@ -4,7 +4,7 @@ comments: true
# 7.1 &nbsp; Binary tree
A <u>binary tree</u> is a non-linear data structure that represents the ancestral and descendent relationships, embodying the "divide and conquer" logic. Similar to a linked list, the basic unit of a binary tree is a node, each containing a value, a reference to the left child node, and a reference to the right child node.
A <u>binary tree</u> is a non-linear data structure that represents the hierarchical relationship between ancestors and descendants, embodying the divide-and-conquer logic of "splitting into two". Similar to a linked list, the basic unit of a binary tree is a node, each containing a value, a reference to the left child node, and a reference to the right child node.
=== "Python"
@@ -218,7 +218,7 @@ The commonly used terminology of binary trees is shown in Figure 7-2.
- <u>Leaf node</u>: A node with no children, both of its pointers point to `None`.
- <u>Edge</u>: The line segment connecting two nodes, i.e., node reference (pointer).
- The <u>level</u> of a node: Incrementing from top to bottom, with the root node's level being 1.
- The <u>degree</u> of a node: The number of a node's children. In a binary tree, the degree can be 0, 1, or 2.
- The <u>degree</u> of a node: The number of children a node has. In a binary tree, the degree can be 0, 1, or 2.
- The <u>height</u> of a binary tree: The number of edges passed from the root node to the farthest leaf node.
- The <u>depth</u> of a node: The number of edges passed from the root node to the node.
- The <u>height</u> of a node: The number of edges from the farthest leaf node to the node.
@@ -229,13 +229,13 @@ The commonly used terminology of binary trees is shown in Figure 7-2.
!!! tip
Please note that we usually define "height" and "depth" as "the number of edges passed," but some problems or textbooks may define them as "the number of nodes passed." In this case, both height and depth need to be incremented by 1.
Please note that we typically define "height" and "depth" as "the number of edges traversed", but some problems or textbooks may define them as "the number of nodes traversed". In such cases, both height and depth need to be incremented by 1.
## 7.1.2 &nbsp; Basic operations of binary trees
### 1. &nbsp; Initializing a binary tree
Similar to a linked list, initialize nodes first, then construct references (pointers).
Similar to a linked list, begin by initialize nodes, then construct references (pointers).
=== "Python"
@@ -619,13 +619,13 @@ Similar to a linked list, inserting and removing nodes in a binary tree can be a
!!! 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.
It's important to note that inserting nodes may change the original logical structure of the binary tree, while removing nodes typically involves removing the node and all its subtrees. Therefore, in a binary tree, insertion and removal are usually performed through a coordinated set of operations to achieve meaningful outcomes.
## 7.1.3 &nbsp; Common types of binary trees
### 1. &nbsp; Perfect binary tree
As shown in Figure 7-4, in a <u>perfect binary tree</u>, all levels of nodes are fully filled. In a perfect binary tree, the degree of leaf nodes is $0$, and the degree of all other nodes is $2$; if the tree's height is $h$, then the total number of nodes is $2^{h+1} - 1$, showing a standard exponential relationship, reflecting the common phenomenon of cell division in nature.
As shown in Figure 7-4, in a <u>perfect binary tree</u>, all levels of nodes are fully filled. In a perfect binary tree, the degree of leaf nodes is $0$, while the degree of all other nodes is $2$; if the tree's height is $h$, then the total number of nodes is $2^{h+1} - 1$, showing a standard exponential relationship, reflecting the common phenomenon of cell division in nature.
!!! tip
@@ -661,7 +661,7 @@ As shown in Figure 7-7, in a <u>balanced binary tree</u>, the absolute differenc
## 7.1.4 &nbsp; Degeneration of binary trees
Figure 7-8 shows the ideal and degenerate structures of binary trees. When every level of a binary tree is filled, it reaches the "perfect binary tree"; when all nodes are biased towards one side, the binary tree degenerates into a "linked list".
Figure 7-8 shows the ideal and degenerate structures of binary trees. A binary tree becomes a "perfect binary tree" when every level is filled; while it degenerates into a "linked list" when all nodes are biased toward one side.
- The perfect binary tree is the ideal situation, fully leveraging the "divide and conquer" advantage of binary trees.
- A linked list is another extreme, where operations become linear, degrading the time complexity to $O(n)$.