mirror of
https://github.com/krahets/hello-algo.git
synced 2025-07-05 21:19:41 +08:00
build
This commit is contained in:
@ -56,6 +56,7 @@ comments: true
|
||||
| front of the queue | 队首 | 佇列首 |
|
||||
| rear of the queue | 队尾 | 佇列尾 |
|
||||
| hash table | 哈希表 | 雜湊表 |
|
||||
| hash set | 哈希集合 | 雜湊集合 |
|
||||
| bucket | 桶 | 桶 |
|
||||
| hash function | 哈希函数 | 雜湊函式 |
|
||||
| hash collision | 哈希冲突 | 雜湊衝突 |
|
||||
|
@ -1555,7 +1555,7 @@ comments: true
|
||||
|
||||
单向链表通常用于实现栈、队列、哈希表和图等数据结构。
|
||||
|
||||
- **栈与队列**:当插入和删除操作都在链表的一端进行时,它表现出先进后出的特性,对应栈;当插入操作在链表的一端进行,删除操作在链表的另一端进行,它表现出先进先出的特性,对应队列。
|
||||
- **栈与队列**:当插入和删除操作都在链表的一端进行时,它表现的特性为先进后出,对应栈;当插入操作在链表的一端进行,删除操作在链表的另一端进行,它表现的特性为先进先出,对应队列。
|
||||
- **哈希表**:链式地址是解决哈希冲突的主流方案之一,在该方案中,所有冲突的元素都会被放到一个链表中。
|
||||
- **图**:邻接表是表示图的一种常用方式,其中图的每个顶点都与一个链表相关联,链表中的每个元素都代表与该顶点相连的其他顶点。
|
||||
|
||||
|
@ -36,7 +36,7 @@ status: new
|
||||
|
||||
<p align="center"> 图 4-9 计算机存储系统 </p>
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
计算机的存储层次结构体现了速度、容量和成本三者之间的精妙平衡。实际上,这种权衡普遍存在于所有工业领域,它要求我们在不同的优势和限制之间找到最佳平衡点。
|
||||
|
||||
|
@ -538,7 +538,7 @@ comments: true
|
||||
|
||||
<p align="center"> 图 13-7 重复排列 </p>
|
||||
|
||||
那么如何去除重复的排列呢?最直接地,考虑借助一个哈希表,直接对排列结果进行去重。然而这样做不够优雅,**因为生成重复排列的搜索分支没有必要,应当提前识别并剪枝**,这样可以进一步提升算法效率。
|
||||
那么如何去除重复的排列呢?最直接地,考虑借助一个哈希集合,直接对排列结果进行去重。然而这样做不够优雅,**因为生成重复排列的搜索分支没有必要,应当提前识别并剪枝**,这样可以进一步提升算法效率。
|
||||
|
||||
### 1. 相等元素剪枝
|
||||
|
||||
@ -554,7 +554,7 @@ comments: true
|
||||
|
||||
### 2. 代码实现
|
||||
|
||||
在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希表 `duplicated` ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝:
|
||||
在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希集合 `duplicated` ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝:
|
||||
|
||||
=== "Python"
|
||||
|
||||
|
@ -11,7 +11,7 @@ comments: true
|
||||
- 回溯问题通常包含多个约束条件,它们可用于实现剪枝操作。剪枝可以提前结束不必要的搜索分支,大幅提升搜索效率。
|
||||
- 回溯算法主要可用于解决搜索问题和约束满足问题。组合优化问题虽然可以用回溯算法解决,但往往存在效率更高或效果更好的解法。
|
||||
- 全排列问题旨在搜索给定集合元素的所有可能的排列。我们借助一个数组来记录每个元素是否被选择,剪掉重复选择同一元素的搜索分支,确保每个元素只被选择一次。
|
||||
- 在全排列问题中,如果集合中存在重复元素,则最终结果会出现重复排列。我们需要约束相等元素在每轮中只能被选择一次,这通常借助一个哈希表来实现。
|
||||
- 在全排列问题中,如果集合中存在重复元素,则最终结果会出现重复排列。我们需要约束相等元素在每轮中只能被选择一次,这通常借助一个哈希集合来实现。
|
||||
- 子集和问题的目标是在给定集合中找到和为目标值的所有子集。集合不区分元素顺序,而搜索过程会输出所有顺序的结果,产生重复子集。我们在回溯前将数据进行排序,并设置一个变量来指示每一轮的遍历起始点,从而将生成重复子集的搜索分支进行剪枝。
|
||||
- 对于子集和问题,数组中的相等元素会产生重复集合。我们利用数组已排序的前置条件,通过判断相邻元素是否相等实现剪枝,从而确保相等元素在每轮中只能被选中一次。
|
||||
- $n$ 皇后问题旨在寻找将 $n$ 个皇后放置到 $n \times n$ 尺寸棋盘上的方案,要求所有皇后两两之间无法攻击对方。该问题的约束条件有行约束、列约束、主对角线和次对角线约束。为满足行约束,我们采用按行放置的策略,保证每一行放置一个皇后。
|
||||
|
@ -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))$ 。
|
||||
|
||||
|
@ -4,7 +4,7 @@ comments: true
|
||||
|
||||
# 3.3 数字编码 *
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
在本书中,标题带有 * 符号的是选读章节。如果你时间有限或感到理解困难,可以先跳过,等学完必读章节后再单独攻克。
|
||||
|
||||
|
@ -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 深度优先遍历
|
||||
|
||||
@ -540,7 +544,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先
|
||||
|
||||
### 1. 算法实现
|
||||
|
||||
这种“走到尽头再返回”的算法范式通常基于递归来实现。与广度优先遍历类似,在深度优先遍历中,我们也需要借助一个哈希表 `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|)$ 空间。
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -11,3 +11,16 @@ comments: true
|
||||
- 算法是在有限时间内解决特定问题的一组指令或操作步骤,而数据结构是计算机中组织和存储数据的方式。
|
||||
- 数据结构与算法紧密相连。数据结构是算法的基石,而算法是数据结构发挥作用的舞台。
|
||||
- 我们可以将数据结构与算法类比为拼装积木,积木代表数据,积木的形状和连接方式等代表数据结构,拼装积木的步骤则对应算法。
|
||||
|
||||
### 1. Q & A
|
||||
|
||||
**Q**:作为一名程序员,我在日常工作中从未用算法解决过问题,常用算法都被编程语言封装好了,直接用就可以了;这是否意味着我们工作中的问题还没有到达需要算法的程度?
|
||||
|
||||
如果把具体的工作技能比作是武功的“招式”的话,那么基础科目应该更像是“内功”。
|
||||
|
||||
我认为学算法(以及其他基础科目)的意义不是在于在工作中从零实现它,而是基于学到的知识,在解决问题时能够作出专业的反应和判断,从而提升工作的整体质量。举一个简单例子,每种编程语言都内置了排序函数:
|
||||
|
||||
- 如果我们没有学过数据结构与算法,那么给定任何数据,我们可能都塞给这个排序函数去做了。运行顺畅、性能不错,看上去并没有什么问题。
|
||||
- 但如果学过算法,我们就会知道内置排序函数的时间复杂度是 $O(n \log n)$ ;而如果给定的数据是固定位数的整数(例如学号),那么我们就可以用效率更高的“基数排序”来做,将时间复杂度降为 $O(nk)$ ,其中 $k$ 为位数。当数据体量很大时,节省出来的运行时间就能创造较大价值(成本降低、体验变好等)。
|
||||
|
||||
在工程领域中,大量问题是难以达到最优解的,许多问题只是被“差不多”地解决了。问题的难易程度一方面取决于问题本身的性质,另一方面也取决于观测问题的人的知识储备。人的知识越完备、经验越多,分析问题就会越深入,问题就能被解决得更优雅。
|
||||
|
@ -681,7 +681,7 @@ AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
设平衡因子为 $f$ ,则一棵 AVL 树的任意节点的平衡因子皆满足 $-1 \le f \le 1$ 。
|
||||
|
||||
|
@ -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
|
||||
|
||||
需要注意的是,插入节点可能会改变二叉树的原有逻辑结构,而删除节点通常意味着删除该节点及其所有子树。因此,在二叉树中,插入与删除通常是由一套操作配合完成的,以实现有实际意义的操作。
|
||||
|
||||
|
@ -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 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.
|
||||
|
||||
|
@ -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))$.
|
||||
|
||||
|
@ -4,7 +4,7 @@ comments: true
|
||||
|
||||
# 3.3 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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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$.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -1555,7 +1555,7 @@ comments: true
|
||||
|
||||
單向鏈結串列通常用於實現堆疊、佇列、雜湊表和圖等資料結構。
|
||||
|
||||
- **堆疊與佇列**:當插入和刪除操作都在鏈結串列的一端進行時,它表現出先進後出的特性,對應堆疊;當插入操作在鏈結串列的一端進行,刪除操作在鏈結串列的另一端進行,它表現出先進先出的特性,對應佇列。
|
||||
- **堆疊與佇列**:當插入和刪除操作都在鏈結串列的一端進行時,它表現的特性為先進後出,對應堆疊;當插入操作在鏈結串列的一端進行,刪除操作在鏈結串列的另一端進行,它表現的特性為先進先出,對應佇列。
|
||||
- **雜湊表**:鏈式位址是解決雜湊衝突的主流方案之一,在該方案中,所有衝突的元素都會被放到一個鏈結串列中。
|
||||
- **圖**:鄰接表是表示圖的一種常用方式,其中圖的每個頂點都與一個鏈結串列相關聯,鏈結串列中的每個元素都代表與該頂點相連的其他頂點。
|
||||
|
||||
|
@ -36,7 +36,7 @@ status: new
|
||||
|
||||
<p align="center"> 圖 4-9 計算機儲存系統 </p>
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
計算機的儲存層次結構體現了速度、容量和成本三者之間的精妙平衡。實際上,這種權衡普遍存在於所有工業領域,它要求我們在不同的優勢和限制之間找到最佳平衡點。
|
||||
|
||||
|
@ -538,7 +538,7 @@ comments: true
|
||||
|
||||
<p align="center"> 圖 13-7 重複排列 </p>
|
||||
|
||||
那麼如何去除重複的排列呢?最直接地,考慮藉助一個雜湊表,直接對排列結果進行去重。然而這樣做不夠優雅,**因為生成重複排列的搜尋分支沒有必要,應當提前識別並剪枝**,這樣可以進一步提升演算法效率。
|
||||
那麼如何去除重複的排列呢?最直接地,考慮藉助一個雜湊集合,直接對排列結果進行去重。然而這樣做不夠優雅,**因為生成重複排列的搜尋分支沒有必要,應當提前識別並剪枝**,這樣可以進一步提升演算法效率。
|
||||
|
||||
### 1. 相等元素剪枝
|
||||
|
||||
@ -554,7 +554,7 @@ comments: true
|
||||
|
||||
### 2. 程式碼實現
|
||||
|
||||
在上一題的程式碼的基礎上,我們考慮在每一輪選擇中開啟一個雜湊表 `duplicated` ,用於記錄該輪中已經嘗試過的元素,並將重複元素剪枝:
|
||||
在上一題的程式碼的基礎上,我們考慮在每一輪選擇中開啟一個雜湊集合 `duplicated` ,用於記錄該輪中已經嘗試過的元素,並將重複元素剪枝:
|
||||
|
||||
=== "Python"
|
||||
|
||||
|
@ -11,7 +11,7 @@ comments: true
|
||||
- 回溯問題通常包含多個約束條件,它們可用於實現剪枝操作。剪枝可以提前結束不必要的搜尋分支,大幅提升搜尋效率。
|
||||
- 回溯演算法主要可用於解決搜尋問題和約束滿足問題。組合最佳化問題雖然可以用回溯演算法解決,但往往存在效率更高或效果更好的解法。
|
||||
- 全排列問題旨在搜尋給定集合元素的所有可能的排列。我們藉助一個陣列來記錄每個元素是否被選擇,剪掉重複選擇同一元素的搜尋分支,確保每個元素只被選擇一次。
|
||||
- 在全排列問題中,如果集合中存在重複元素,則最終結果會出現重複排列。我們需要約束相等元素在每輪中只能被選擇一次,這通常藉助一個雜湊表來實現。
|
||||
- 在全排列問題中,如果集合中存在重複元素,則最終結果會出現重複排列。我們需要約束相等元素在每輪中只能被選擇一次,這通常藉助一個雜湊集合來實現。
|
||||
- 子集和問題的目標是在給定集合中找到和為目標值的所有子集。集合不區分元素順序,而搜尋過程會輸出所有順序的結果,產生重複子集。我們在回溯前將資料進行排序,並設定一個變數來指示每一輪的走訪起始點,從而將生成重複子集的搜尋分支進行剪枝。
|
||||
- 對於子集和問題,陣列中的相等元素會產生重複集合。我們利用陣列已排序的前置條件,透過判斷相鄰元素是否相等實現剪枝,從而確保相等元素在每輪中只能被選中一次。
|
||||
- $n$ 皇后問題旨在尋找將 $n$ 個皇后放置到 $n \times n$ 尺寸棋盤上的方案,要求所有皇后兩兩之間無法攻擊對方。該問題的約束條件有行約束、列約束、主對角線和次對角線約束。為滿足行約束,我們採用按行放置的策略,保證每一行放置一個皇后。
|
||||
|
@ -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))$ 。
|
||||
|
||||
|
@ -4,7 +4,7 @@ comments: true
|
||||
|
||||
# 3.3 數字編碼 *
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
在本書中,標題帶有 * 符號的是選讀章節。如果你時間有限或感到理解困難,可以先跳過,等學完必讀章節後再單獨攻克。
|
||||
|
||||
|
@ -1130,7 +1130,80 @@ comments: true
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="graph_adjacency_matrix.rb"
|
||||
[class]{GraphAdjMat}-[func]{}
|
||||
### 基於鄰接矩陣實現的無向圖類別 ###
|
||||
class GraphAdjMat
|
||||
def initialize(vertices, edges)
|
||||
### 建構子 ###
|
||||
# 頂點串列,元素代表“頂點值”,索引代表“頂點索引”
|
||||
@vertices = []
|
||||
# 鄰接矩陣,行列索引對應“頂點索引”
|
||||
@adj_mat = []
|
||||
# 新增頂點
|
||||
vertices.each { |val| add_vertex(val) }
|
||||
# 新增邊
|
||||
# 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引
|
||||
edges.each { |e| add_edge(e[0], e[1]) }
|
||||
end
|
||||
|
||||
### 獲取頂點數量 ###
|
||||
def size
|
||||
@vertices.length
|
||||
end
|
||||
|
||||
### 新增頂點 ###
|
||||
def add_vertex(val)
|
||||
n = size
|
||||
# 向頂點串列中新增新頂點的值
|
||||
@vertices << val
|
||||
# 在鄰接矩陣中新增一行
|
||||
new_row = Array.new(n, 0)
|
||||
@adj_mat << new_row
|
||||
# 在鄰接矩陣中新增一列
|
||||
@adj_mat.each { |row| row << 0 }
|
||||
end
|
||||
|
||||
### 刪除頂點 ###
|
||||
def remove_vertex(index)
|
||||
raise IndexError if index >= size
|
||||
|
||||
# 在頂點串列中移除索引 index 的頂點
|
||||
@vertices.delete_at(index)
|
||||
# 在鄰接矩陣中刪除索引 index 的行
|
||||
@adj_mat.delete_at(index)
|
||||
# 在鄰接矩陣中刪除索引 index 的列
|
||||
@adj_mat.each { |row| row.delete_at(index) }
|
||||
end
|
||||
|
||||
### 新增邊 ###
|
||||
def add_edge(i, j)
|
||||
# 參數 i, j 對應 vertices 元素索引
|
||||
# 索引越界與相等處理
|
||||
if i < 0 || j < 0 || i >= size || j >= size || i == j
|
||||
raise IndexError
|
||||
end
|
||||
# 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i)
|
||||
@adj_mat[i][j] = 1
|
||||
@adj_mat[j][i] = 1
|
||||
end
|
||||
|
||||
### 刪除邊 ###
|
||||
def remove_edge(i, j)
|
||||
# 參數 i, j 對應 vertices 元素索引
|
||||
# 索引越界與相等處理
|
||||
if i < 0 || j < 0 || i >= size || j >= size || i == j
|
||||
raise IndexError
|
||||
end
|
||||
@adj_mat[i][j] = 0
|
||||
@adj_mat[j][i] = 0
|
||||
end
|
||||
|
||||
### 列印鄰接矩陣 ###
|
||||
def __print__
|
||||
puts "頂點串列 = #{@vertices}"
|
||||
puts '鄰接矩陣 ='
|
||||
print_matrix(@adj_mat)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
@ -2233,7 +2306,73 @@ comments: true
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="graph_adjacency_list.rb"
|
||||
[class]{GraphAdjList}-[func]{}
|
||||
### 基於鄰接表實現的無向圖類別 ###
|
||||
class GraphAdjList
|
||||
attr_reader :adj_list
|
||||
|
||||
### 建構子 ###
|
||||
def initialize(edges)
|
||||
# 鄰接表,key:頂點,value:該頂點的所有鄰接頂點
|
||||
@adj_list = {}
|
||||
# 新增所有頂點和邊
|
||||
for edge in edges
|
||||
add_vertex(edge[0])
|
||||
add_vertex(edge[1])
|
||||
add_edge(edge[0], edge[1])
|
||||
end
|
||||
end
|
||||
|
||||
### 獲取頂點數量 ###
|
||||
def size
|
||||
@adj_list.length
|
||||
end
|
||||
|
||||
### 新增邊 ###
|
||||
def add_edge(vet1, vet2)
|
||||
raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)
|
||||
|
||||
@adj_list[vet1] << vet2
|
||||
@adj_list[vet2] << vet1
|
||||
end
|
||||
|
||||
### 刪除邊 ###
|
||||
def remove_edge(vet1, vet2)
|
||||
raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)
|
||||
|
||||
# 刪除邊 vet1 - vet2
|
||||
@adj_list[vet1].delete(vet2)
|
||||
@adj_list[vet2].delete(vet1)
|
||||
end
|
||||
|
||||
### 新增頂點 ###
|
||||
def add_vertex(vet)
|
||||
return if @adj_list.include?(vet)
|
||||
|
||||
# 在鄰接表中新增一個新鏈結串列
|
||||
@adj_list[vet] = []
|
||||
end
|
||||
|
||||
### 刪除頂點 ###
|
||||
def remove_vertex(vet)
|
||||
raise ArgumentError unless @adj_list.include?(vet)
|
||||
|
||||
# 在鄰接表中刪除頂點 vet 對應的鏈結串列
|
||||
@adj_list.delete(vet)
|
||||
# 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊
|
||||
for vertex in @adj_list
|
||||
@adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)
|
||||
end
|
||||
end
|
||||
|
||||
### 列印鄰接表 ###
|
||||
def __print__
|
||||
puts '鄰接表 ='
|
||||
for vertex in @adj_list
|
||||
tmp = @adj_list[vertex.first].map { |v| v.val }
|
||||
puts "#{vertex.first.val}: #{tmp},"
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
@ -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
|
||||
@ -447,7 +451,29 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="graph_bfs.rb"
|
||||
[class]{}-[func]{graph_bfs}
|
||||
### 廣度優先走訪 ###
|
||||
def graph_bfs(graph, start_vet)
|
||||
# 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點
|
||||
# 頂點走訪序列
|
||||
res = []
|
||||
# 雜湊集合,用於記錄已被訪問過的頂點
|
||||
visited = Set.new([start_vet])
|
||||
# 佇列用於實現 BFS
|
||||
que = [start_vet]
|
||||
# 以頂點 vet 為起點,迴圈直至訪問完所有頂點
|
||||
while que.length > 0
|
||||
vet = que.shift # 佇列首頂點出隊
|
||||
res << vet # 記錄訪問頂點
|
||||
# 走訪該頂點的所有鄰接頂點
|
||||
for adj_vet in graph.adj_list[vet]
|
||||
next if visited.include?(adj_vet) # 跳過已被訪問的頂點
|
||||
que << adj_vet # 只入列未訪問的頂點
|
||||
visited.add(adj_vet) # 標記該頂點已被訪問
|
||||
end
|
||||
end
|
||||
# 返回頂點走訪序列
|
||||
res
|
||||
end
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
@ -506,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 深度優先走訪
|
||||
|
||||
@ -518,7 +544,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
|
||||
|
||||
### 1. 演算法實現
|
||||
|
||||
這種“走到盡頭再返回”的演算法範式通常基於遞迴來實現。與廣度優先走訪類似,在深度優先走訪中,我們也需要藉助一個雜湊表 `visited` 來記錄已被訪問的頂點,以避免重複訪問頂點。
|
||||
這種“走到盡頭再返回”的演算法範式通常基於遞迴來實現。與廣度優先走訪類似,在深度優先走訪中,我們也需要藉助一個雜湊集合 `visited` 來記錄已被訪問的頂點,以避免重複訪問頂點。
|
||||
|
||||
=== "Python"
|
||||
|
||||
@ -539,7 +565,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
|
||||
# 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點
|
||||
# 頂點走訪序列
|
||||
res = []
|
||||
# 雜湊表,用於記錄已被訪問過的頂點
|
||||
# 雜湊集合,用於記錄已被訪問過的頂點
|
||||
visited = set[Vertex]()
|
||||
dfs(graph, visited, res, start_vet)
|
||||
return res
|
||||
@ -566,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;
|
||||
@ -594,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;
|
||||
@ -623,7 +649,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
|
||||
List<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {
|
||||
// 頂點走訪序列
|
||||
List<Vertex> res = [];
|
||||
// 雜湊表,用於記錄已被訪問過的頂點
|
||||
// 雜湊集合,用於記錄已被訪問過的頂點
|
||||
HashSet<Vertex> visited = [];
|
||||
DFS(graph, visited, res, startVet);
|
||||
return res;
|
||||
@ -653,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)
|
||||
// 返回頂點走訪序列
|
||||
@ -683,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
|
||||
@ -713,7 +739,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
|
||||
function graphDFS(graph, startVet) {
|
||||
// 頂點走訪序列
|
||||
const res = [];
|
||||
// 雜湊表,用於記錄已被訪問過的頂點
|
||||
// 雜湊集合,用於記錄已被訪問過的頂點
|
||||
const visited = new Set();
|
||||
dfs(graph, visited, res, startVet);
|
||||
return res;
|
||||
@ -747,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;
|
||||
@ -780,7 +806,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
|
||||
List<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {
|
||||
// 頂點走訪序列
|
||||
List<Vertex> res = [];
|
||||
// 雜湊表,用於記錄已被訪問過的頂點
|
||||
// 雜湊集合,用於記錄已被訪問過的頂點
|
||||
Set<Vertex> visited = {};
|
||||
dfs(graph, visited, res, startVet);
|
||||
return res;
|
||||
@ -811,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);
|
||||
|
||||
@ -882,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
|
||||
@ -892,9 +918,28 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="graph_dfs.rb"
|
||||
[class]{}-[func]{dfs}
|
||||
### 深度優先走訪輔助函式 ###
|
||||
def dfs(graph, visited, res, vet)
|
||||
res << vet # 記錄訪問頂點
|
||||
visited.add(vet) # 標記該頂點已被訪問
|
||||
# 走訪該頂點的所有鄰接頂點
|
||||
for adj_vet in graph.adj_list[vet]
|
||||
next if visited.include?(adj_vet) # 跳過已被訪問的頂點
|
||||
# 遞迴訪問鄰接頂點
|
||||
dfs(graph, visited, res, adj_vet)
|
||||
end
|
||||
end
|
||||
|
||||
[class]{}-[func]{graph_dfs}
|
||||
### 深度優先走訪 ###
|
||||
def graph_dfs(graph, start_vet)
|
||||
# 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點
|
||||
# 頂點走訪序列
|
||||
res = []
|
||||
# 雜湊集合,用於記錄已被訪問過的頂點
|
||||
visited = Set.new
|
||||
dfs(graph, visited, res, start_vet)
|
||||
res
|
||||
end
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
@ -962,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|)$ 空間。
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -11,3 +11,16 @@ comments: true
|
||||
- 演算法是在有限時間內解決特定問題的一組指令或操作步驟,而資料結構是計算機中組織和儲存資料的方式。
|
||||
- 資料結構與演算法緊密相連。資料結構是演算法的基石,而演算法是資料結構發揮作用的舞臺。
|
||||
- 我們可以將資料結構與演算法類比為拼裝積木,積木代表資料,積木的形狀和連線方式等代表資料結構,拼裝積木的步驟則對應演算法。
|
||||
|
||||
### 1. Q & A
|
||||
|
||||
**Q**:作為一名程式設計師,我在日常工作中從未用演算法解決過問題,常用演算法都被程式語言封裝好了,直接用就可以了;這是否意味著我們工作中的問題還沒有到達需要演算法的程度?
|
||||
|
||||
如果把具體的工作技能比作是武功的“招式”的話,那麼基礎科目應該更像是“內功”。
|
||||
|
||||
我認為學演算法(以及其他基礎科目)的意義不是在於在工作中從零實現它,而是基於學到的知識,在解決問題時能夠作出專業的反應和判斷,從而提升工作的整體質量。舉一個簡單例子,每種程式語言都內建了排序函式:
|
||||
|
||||
- 如果我們沒有學過資料結構與演算法,那麼給定任何資料,我們可能都塞給這個排序函式去做了。執行順暢、效能不錯,看上去並沒有什麼問題。
|
||||
- 但如果學過演算法,我們就會知道內建排序函式的時間複雜度是 $O(n \log n)$ ;而如果給定的資料是固定位數的整數(例如學號),那麼我們就可以用效率更高的“基數排序”來做,將時間複雜度降為 $O(nk)$ ,其中 $k$ 為位數。當資料體量很大時,節省出來的執行時間就能創造較大價值(成本降低、體驗變好等)。
|
||||
|
||||
在工程領域中,大量問題是難以達到最優解的,許多問題只是被“差不多”地解決了。問題的難易程度一方面取決於問題本身的性質,另一方面也取決於觀測問題的人的知識儲備。人的知識越完備、經驗越多,分析問題就會越深入,問題就能被解決得更優雅。
|
||||
|
@ -681,7 +681,7 @@ AVL 樹既是二元搜尋樹,也是平衡二元樹,同時滿足這兩類二
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
設平衡因子為 $f$ ,則一棵 AVL 樹的任意節點的平衡因子皆滿足 $-1 \le f \le 1$ 。
|
||||
|
||||
|
@ -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%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%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%E7%AF%80%E9%BB%9E%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%E7%AF%80%E9%BB%9E%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%E7%AF%80%E9%BB%9E%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%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%AF%80%E9%BB%9E%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%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E6%A8%99%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%E8%88%87%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%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%96%93%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%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%AA%E9%99%A4%E7%AF%80%E9%BB%9E%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%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%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%E7%AF%80%E9%BB%9E%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%E7%AF%80%E9%BB%9E%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%E7%AF%80%E9%BB%9E%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%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%AF%80%E9%BB%9E%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%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E6%A8%99%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%E8%88%87%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%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%96%93%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%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%AA%E9%99%A4%E7%AF%80%E9%BB%9E%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
|
||||
|
||||
需要注意的是,插入節點可能會改變二元樹的原有邏輯結構,而刪除節點通常意味著刪除該節點及其所有子樹。因此,在二元樹中,插入與刪除通常是由一套操作配合完成的,以實現有實際意義的操作。
|
||||
|
||||
|
Reference in New Issue
Block a user