diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 7d5737177..de5b4cc63 100755 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -492,7 +492,7 @@ comments: true // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 while i <= mid && j <= right { if nums[i] <= nums[j] { - tmp[k] = nums[j]; + tmp[k] = nums[i]; i += 1; } else { tmp[k] = nums[j]; diff --git a/docs/chapter_tree/array_representation_of_tree.md b/docs/chapter_tree/array_representation_of_tree.md index 99fcbffc6..75aab4ab5 100644 --- a/docs/chapter_tree/array_representation_of_tree.md +++ b/docs/chapter_tree/array_representation_of_tree.md @@ -18,7 +18,7 @@ comments: true

图 7-12   完美二叉树的数组表示

-**映射公式的角色相当于链表中的引用**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。 +**映射公式的角色相当于链表中的节点引用(指针)**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。 ## 7.3.2   表示任意二叉树 diff --git a/en/docs/chapter_graph/graph.md b/en/docs/chapter_graph/graph.md new file mode 100644 index 000000000..bfd0de110 --- /dev/null +++ b/en/docs/chapter_graph/graph.md @@ -0,0 +1,103 @@ +--- +comments: true +--- + +# 9.1   Graph + +A "graph" is a type of nonlinear data structure, consisting of "vertices" and "edges". A graph $G$ can be abstractly represented as a collection of a set of vertices $V$ and a set of edges $E$. The following example shows a graph containing 5 vertices and 7 edges. + +$$ +\begin{aligned} +V & = \{ 1, 2, 3, 4, 5 \} \newline +E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline +G & = \{ V, E \} \newline +\end{aligned} +$$ + +If vertices are viewed as nodes and edges as references (pointers) connecting the nodes, graphs can be seen as a data structure that extends from linked lists. As shown below, **compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) are more complex due to their higher degree of freedom**. + +![Relationship between linked lists, trees, and graphs](graph.assets/linkedlist_tree_graph.png){ class="animation-figure" } + +

Figure 9-1   Relationship between linked lists, trees, and graphs

+ +## 9.1.1   Common types of graphs + +Based on whether edges have direction, graphs can be divided into "undirected graphs" and "directed graphs", as shown below. + +- In undirected graphs, edges represent a "bidirectional" connection between two vertices, for example, the "friendship" in WeChat or QQ. +- In directed graphs, edges have directionality, that is, the edges $A \rightarrow B$ and $A \leftarrow B$ are independent of each other, for example, the "follow" and "be followed" relationship on Weibo or TikTok. + +![Directed and undirected graphs](graph.assets/directed_graph.png){ class="animation-figure" } + +

Figure 9-2   Directed and undirected graphs

+ +Based on whether all vertices are connected, graphs can be divided into "connected graphs" and "disconnected graphs", as shown below. + +- For connected graphs, it is possible to reach any other vertex starting from a certain vertex. +- For disconnected graphs, there is at least one vertex that cannot be reached from a certain starting vertex. + +![Connected and disconnected graphs](graph.assets/connected_graph.png){ class="animation-figure" } + +

Figure 9-3   Connected and disconnected graphs

+ +We can also add a "weight" variable to edges, resulting in "weighted graphs" as shown below. For example, in mobile games like "Honor of Kings", the system calculates the "closeness" between players based on shared gaming time, and this closeness network can be represented with a weighted graph. + +![Weighted and unweighted graphs](graph.assets/weighted_graph.png){ class="animation-figure" } + +

Figure 9-4   Weighted and unweighted graphs

+ +Graph data structures include the following commonly used terms. + +- "Adjacency": When there is an edge connecting two vertices, these two vertices are said to be "adjacent". In the above figure, the adjacent vertices of vertex 1 are vertices 2, 3, and 5. +- "Path": The sequence of edges passed from vertex A to vertex B is called a "path" from A to B. In the above figure, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4. +- "Degree": The number of edges a vertex has. For directed graphs, "in-degree" refers to how many edges point to the vertex, and "out-degree" refers to how many edges point out from the vertex. + +## 9.1.2   Representation of graphs + +Common representations of graphs include "adjacency matrices" and "adjacency lists". The following examples use undirected graphs. + +### 1.   Adjacency matrix + +Let the number of vertices in the graph be $n$, the "adjacency matrix" uses an $n \times n$ matrix to represent the graph, where each row (column) represents a vertex, and the matrix elements represent edges, with $1$ or $0$ indicating whether there is an edge between two vertices. + +As shown below, let the adjacency matrix be $M$, and the list of vertices be $V$, then the matrix element $M[i, j] = 1$ indicates there is an edge between vertex $V[i]$ and vertex $V[j]$, conversely $M[i, j] = 0$ indicates there is no edge between the two vertices. + +![Representation of a graph with an adjacency matrix](graph.assets/adjacency_matrix.png){ class="animation-figure" } + +

Figure 9-5   Representation of a graph with an adjacency matrix

+ +Adjacency matrices have the following characteristics. + +- A vertex cannot be connected to itself, so the elements on the main diagonal of the adjacency matrix are meaningless. +- For undirected graphs, edges in both directions are equivalent, thus the adjacency matrix is symmetric about the main diagonal. +- By replacing the elements of the adjacency matrix from $1$ and $0$ to weights, it can represent weighted graphs. + +When representing graphs with adjacency matrices, it is possible to directly access matrix elements to obtain edges, thus operations of addition, deletion, lookup, and modification are very efficient, all with a time complexity of $O(1)$. However, the space complexity of the matrix is $O(n^2)$, which consumes more memory. + +### 2.   Adjacency list + +The "adjacency list" uses $n$ linked lists to represent the graph, with each linked list node representing a vertex. The $i$-th linked list corresponds to vertex $i$ and contains all adjacent vertices (vertices connected to that vertex). The Figure 9-6 shows an example of a graph stored using an adjacency list. + +![Representation of a graph with an adjacency list](graph.assets/adjacency_list.png){ class="animation-figure" } + +

Figure 9-6   Representation of a graph with an adjacency list

+ +The adjacency list only stores actual edges, and the total number of edges is often much less than $n^2$, making it more space-efficient. However, finding edges in the adjacency list requires traversing the linked list, so its time efficiency is not as good as that of the adjacency matrix. + +Observing the above figure, **the structure of the adjacency list is very similar to the "chaining" in hash tables, hence we can use similar methods to optimize efficiency**. For example, when the linked list is long, it can be transformed into an AVL tree or red-black tree, thus optimizing the time efficiency from $O(n)$ to $O(\log n)$; the linked list can also be transformed into a hash table, thus reducing the time complexity to $O(1)$. + +## 9.1.3   Common applications of graphs + +As shown in the Table 9-1 , many real-world systems can be modeled with graphs, and corresponding problems can be reduced to graph computing problems. + +

Table 9-1   Common graphs in real life

+ +
+ +| | Vertices | Edges | Graph Computing Problem | +| --------------- | ---------------- | --------------------------------------------- | -------------------------------- | +| Social Networks | Users | Friendships | Potential Friend Recommendations | +| Subway Lines | Stations | Connectivity Between Stations | Shortest Route Recommendations | +| Solar System | Celestial Bodies | Gravitational Forces Between Celestial Bodies | Planetary Orbit Calculations | + +
diff --git a/en/docs/chapter_graph/graph_operations.md b/en/docs/chapter_graph/graph_operations.md new file mode 100644 index 000000000..fb887187c --- /dev/null +++ b/en/docs/chapter_graph/graph_operations.md @@ -0,0 +1,2266 @@ +--- +comments: true +--- + +# 9.2   Basic operations on graphs + +The basic operations on graphs can be divided into operations on "edges" and operations on "vertices". Under the two representation methods of "adjacency matrix" and "adjacency list", the implementation methods are different. + +## 9.2.1   Implementation based on adjacency matrix + +Given an undirected graph with $n$ vertices, the various operations are implemented as shown in the Figure 9-7 . + +- **Adding or removing an edge**: Directly modify the specified edge in the adjacency matrix, using $O(1)$ time. Since it is an undirected graph, it is necessary to update the edges in both directions simultaneously. +- **Adding a vertex**: Add a row and a column at the end of the adjacency matrix and fill them all with $0$s, using $O(n)$ time. +- **Removing a vertex**: Delete a row and a column in the adjacency matrix. The worst case is when the first row and column are removed, requiring $(n-1)^2$ elements to be "moved up and to the left", thus using $O(n^2)$ time. +- **Initialization**: Pass in $n$ vertices, initialize a vertex list `vertices` of length $n$, using $O(n)$ time; initialize an $n \times n$ size adjacency matrix `adjMat`, using $O(n^2)$ time. + +=== "Initialize adjacency matrix" + ![Initialization, adding and removing edges, adding and removing vertices in adjacency matrix](graph_operations.assets/adjacency_matrix_step1_initialization.png){ class="animation-figure" } + +=== "Add an edge" + ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png){ class="animation-figure" } + +=== "Remove an edge" + ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png){ class="animation-figure" } + +=== "Add a vertex" + ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png){ class="animation-figure" } + +=== "Remove a vertex" + ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png){ class="animation-figure" } + +

Figure 9-7   Initialization, adding and removing edges, adding and removing vertices in adjacency matrix

+ +Below is the implementation code for graphs represented using an adjacency matrix: + +=== "Python" + + ```python title="graph_adjacency_matrix.py" + class GraphAdjMat: + """基于邻接矩阵实现的无向图类""" + + def __init__(self, vertices: list[int], edges: list[list[int]]): + """构造方法""" + # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + self.vertices: list[int] = [] + # 邻接矩阵,行列索引对应“顶点索引” + self.adj_mat: list[list[int]] = [] + # 添加顶点 + for val in vertices: + self.add_vertex(val) + # 添加边 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """获取顶点数量""" + return len(self.vertices) + + def add_vertex(self, val: int): + """添加顶点""" + n = self.size() + # 向顶点列表中添加新顶点的值 + self.vertices.append(val) + # 在邻接矩阵中添加一行 + new_row = [0] * n + self.adj_mat.append(new_row) + # 在邻接矩阵中添加一列 + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """删除顶点""" + if index >= self.size(): + raise IndexError() + # 在顶点列表中移除索引 index 的顶点 + self.vertices.pop(index) + # 在邻接矩阵中删除索引 index 的行 + self.adj_mat.pop(index) + # 在邻接矩阵中删除索引 index 的列 + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """添加边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + def remove_edge(self, i: int, j: int): + """删除边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 + + def print(self): + """打印邻接矩阵""" + print("顶点列表 =", self.vertices) + print("邻接矩阵 =") + print_matrix(self.adj_mat) + ``` + +=== "C++" + + ```cpp title="graph_adjacency_matrix.cpp" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + vector vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vector> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + public: + /* 构造方法 */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // 添加顶点 + for (int val : vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() const { + return vertices.size(); + } + + /* 添加顶点 */ + void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.push_back(val); + // 在邻接矩阵中添加一行 + adjMat.emplace_back(vector(n, 0)); + // 在邻接矩阵中添加一列 + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* 删除顶点 */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("顶点不存在"); + } + // 在顶点列表中移除索引 index 的顶点 + vertices.erase(vertices.begin() + index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.erase(adjMat.begin() + index); + // 在邻接矩阵中删除索引 index 的列 + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + void print() { + cout << "顶点列表 = "; + printVector(vertices); + cout << "邻接矩阵 =" << endl; + printVectorMatrix(adjMat); + } + }; + ``` + +=== "Java" + + ```java title="graph_adjacency_matrix.java" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造方法 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // 添加顶点 + for (int val : vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + public int size() { + return vertices.size(); + } + + /* 添加顶点 */ + public void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.add(val); + // 在邻接矩阵中添加一行 + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // 在邻接矩阵中添加一列 + for (List row : adjMat) { + row.add(0); + } + } + + /* 删除顶点 */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // 在顶点列表中移除索引 index 的顶点 + vertices.remove(index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.remove(index); + // 在邻接矩阵中删除索引 index 的列 + for (List row : adjMat) { + row.remove(index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + public void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat.get(i).set(j, 1); + adjMat.get(j).set(i, 1); + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + public void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + adjMat.get(i).set(j, 0); + adjMat.get(j).set(i, 0); + } + + /* 打印邻接矩阵 */ + public void print() { + System.out.print("顶点列表 = "); + System.out.println(vertices); + System.out.println("邻接矩阵 ="); + PrintUtil.printMatrix(adjMat); + } + } + ``` + +=== "C#" + + ```csharp title="graph_adjacency_matrix.cs" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + foreach (int val in vertices) { + AddVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + foreach (int[] e in edges) { + AddEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + int Size() { + return vertices.Count; + } + + /* 添加顶点 */ + public void AddVertex(int val) { + int n = Size(); + // 向顶点列表中添加新顶点的值 + vertices.Add(val); + // 在邻接矩阵中添加一行 + List newRow = new(n); + for (int j = 0; j < n; j++) { + newRow.Add(0); + } + adjMat.Add(newRow); + // 在邻接矩阵中添加一列 + foreach (List row in adjMat) { + row.Add(0); + } + } + + /* 删除顶点 */ + public void RemoveVertex(int index) { + if (index >= Size()) + throw new IndexOutOfRangeException(); + // 在顶点列表中移除索引 index 的顶点 + vertices.RemoveAt(index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.RemoveAt(index); + // 在邻接矩阵中删除索引 index 的列 + foreach (List row in adjMat) { + row.RemoveAt(index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + public void AddEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + public void RemoveEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + public void Print() { + Console.Write("顶点列表 = "); + PrintUtil.PrintList(vertices); + Console.WriteLine("邻接矩阵 ="); + PrintUtil.PrintMatrix(adjMat); + } + } + ``` + +=== "Go" + + ```go title="graph_adjacency_matrix.go" + /* 基于邻接矩阵实现的无向图类 */ + type graphAdjMat struct { + // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vertices []int + // 邻接矩阵,行列索引对应“顶点索引” + adjMat [][]int + } + + /* 构造函数 */ + func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { + // 添加顶点 + n := len(vertices) + adjMat := make([][]int, n) + for i := range adjMat { + adjMat[i] = make([]int, n) + } + // 初始化图 + g := &graphAdjMat{ + vertices: vertices, + adjMat: adjMat, + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for i := range edges { + g.addEdge(edges[i][0], edges[i][1]) + } + return g + } + + /* 获取顶点数量 */ + func (g *graphAdjMat) size() int { + return len(g.vertices) + } + + /* 添加顶点 */ + func (g *graphAdjMat) addVertex(val int) { + n := g.size() + // 向顶点列表中添加新顶点的值 + g.vertices = append(g.vertices, val) + // 在邻接矩阵中添加一行 + newRow := make([]int, n) + g.adjMat = append(g.adjMat, newRow) + // 在邻接矩阵中添加一列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i], 0) + } + } + + /* 删除顶点 */ + func (g *graphAdjMat) removeVertex(index int) { + if index >= g.size() { + return + } + // 在顶点列表中移除索引 index 的顶点 + g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) + // 在邻接矩阵中删除索引 index 的行 + g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) + // 在邻接矩阵中删除索引 index 的列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + func (g *graphAdjMat) addEdge(i, j int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + g.adjMat[i][j] = 1 + g.adjMat[j][i] = 1 + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + func (g *graphAdjMat) removeEdge(i, j int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + g.adjMat[i][j] = 0 + g.adjMat[j][i] = 0 + } + + /* 打印邻接矩阵 */ + func (g *graphAdjMat) print() { + fmt.Printf("\t顶点列表 = %v\n", g.vertices) + fmt.Printf("\t邻接矩阵 = \n") + for i := range g.adjMat { + fmt.Printf("\t\t\t%v\n", g.adjMat[i]) + } + } + ``` + +=== "Swift" + + ```swift title="graph_adjacency_matrix.swift" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + private var vertices: [Int] // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + private var adjMat: [[Int]] // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造方法 */ + init(vertices: [Int], edges: [[Int]]) { + self.vertices = [] + adjMat = [] + // 添加顶点 + for val in vertices { + addVertex(val: val) + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for e in edges { + addEdge(i: e[0], j: e[1]) + } + } + + /* 获取顶点数量 */ + func size() -> Int { + vertices.count + } + + /* 添加顶点 */ + func addVertex(val: Int) { + let n = size() + // 向顶点列表中添加新顶点的值 + vertices.append(val) + // 在邻接矩阵中添加一行 + let newRow = Array(repeating: 0, count: n) + adjMat.append(newRow) + // 在邻接矩阵中添加一列 + for i in adjMat.indices { + adjMat[i].append(0) + } + } + + /* 删除顶点 */ + func removeVertex(index: Int) { + if index >= size() { + fatalError("越界") + } + // 在顶点列表中移除索引 index 的顶点 + vertices.remove(at: index) + // 在邻接矩阵中删除索引 index 的行 + adjMat.remove(at: index) + // 在邻接矩阵中删除索引 index 的列 + for i in adjMat.indices { + adjMat[i].remove(at: index) + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + func addEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + func removeEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 打印邻接矩阵 */ + func print() { + Swift.print("顶点列表 = ", terminator: "") + Swift.print(vertices) + Swift.print("邻接矩阵 =") + PrintUtil.printMatrix(matrix: adjMat) + } + } + ``` + +=== "JS" + + ```javascript title="graph_adjacency_matrix.js" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + constructor(vertices, edges) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + for (const val of vertices) { + this.addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + size() { + return this.vertices.length; + } + + /* 添加顶点 */ + addVertex(val) { + const n = this.size(); + // 向顶点列表中添加新顶点的值 + this.vertices.push(val); + // 在邻接矩阵中添加一行 + const newRow = []; + for (let j = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在邻接矩阵中添加一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 删除顶点 */ + removeVertex(index) { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在顶点列表中移除索引 index 的顶点 + this.vertices.splice(index, 1); + + // 在邻接矩阵中删除索引 index 的行 + this.adjMat.splice(index, 1); + // 在邻接矩阵中删除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + addEdge(i, j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + removeEdge(i, j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + print() { + console.log('顶点列表 = ', this.vertices); + console.log('邻接矩阵 =', this.adjMat); + } + } + ``` + +=== "TS" + + ```typescript title="graph_adjacency_matrix.ts" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + vertices: number[]; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + adjMat: number[][]; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + constructor(vertices: number[], edges: number[][]) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + for (const val of vertices) { + this.addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + size(): number { + return this.vertices.length; + } + + /* 添加顶点 */ + addVertex(val: number): void { + const n: number = this.size(); + // 向顶点列表中添加新顶点的值 + this.vertices.push(val); + // 在邻接矩阵中添加一行 + const newRow: number[] = []; + for (let j: number = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在邻接矩阵中添加一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 删除顶点 */ + removeVertex(index: number): void { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在顶点列表中移除索引 index 的顶点 + this.vertices.splice(index, 1); + + // 在邻接矩阵中删除索引 index 的行 + this.adjMat.splice(index, 1); + // 在邻接矩阵中删除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + addEdge(i: number, j: number): void { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + removeEdge(i: number, j: number): void { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + print(): void { + console.log('顶点列表 = ', this.vertices); + console.log('邻接矩阵 =', this.adjMat); + } + } + ``` + +=== "Dart" + + ```dart title="graph_adjacency_matrix.dart" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + List vertices = []; // 顶点元素,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat = []; //邻接矩阵,行列索引对应“顶点索引” + + /* 构造方法 */ + GraphAdjMat(List vertices, List> edges) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + for (int val in vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (List e in edges) { + addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + int size() { + return vertices.length; + } + + /* 添加顶点 */ + void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.add(val); + // 在邻接矩阵中添加一行 + List newRow = List.filled(n, 0, growable: true); + adjMat.add(newRow); + // 在邻接矩阵中添加一列 + for (List row in adjMat) { + row.add(0); + } + } + + /* 删除顶点 */ + void removeVertex(int index) { + if (index >= size()) { + throw IndexError; + } + // 在顶点列表中移除索引 index 的顶点 + vertices.removeAt(index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.removeAt(index); + // 在邻接矩阵中删除索引 index 的列 + for (List row in adjMat) { + row.removeAt(index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + void printAdjMat() { + print("顶点列表 = $vertices"); + print("邻接矩阵 = "); + printMatrix(adjMat); + } + } + ``` + +=== "Rust" + + ```rust title="graph_adjacency_matrix.rs" + /* 基于邻接矩阵实现的无向图类型 */ + pub struct GraphAdjMat { + // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + pub vertices: Vec, + // 邻接矩阵,行列索引对应“顶点索引” + pub adj_mat: Vec>, + } + + impl GraphAdjMat { + /* 构造方法 */ + pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { + let mut graph = GraphAdjMat { + vertices: vec![], + adj_mat: vec![], + }; + // 添加顶点 + for val in vertices { + graph.add_vertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for edge in edges { + graph.add_edge(edge[0], edge[1]) + } + + graph + } + + /* 获取顶点数量 */ + pub fn size(&self) -> usize { + self.vertices.len() + } + + /* 添加顶点 */ + pub fn add_vertex(&mut self, val: i32) { + let n = self.size(); + // 向顶点列表中添加新顶点的值 + self.vertices.push(val); + // 在邻接矩阵中添加一行 + self.adj_mat.push(vec![0; n]); + // 在邻接矩阵中添加一列 + for row in &mut self.adj_mat { + row.push(0); + } + } + + /* 删除顶点 */ + pub fn remove_vertex(&mut self, index: usize) { + if index >= self.size() { + panic!("index error") + } + // 在顶点列表中移除索引 index 的顶点 + self.vertices.remove(index); + // 在邻接矩阵中删除索引 index 的行 + self.adj_mat.remove(index); + // 在邻接矩阵中删除索引 index 的列 + for row in &mut self.adj_mat { + row.remove(index); + } + } + + /* 添加边 */ + pub fn add_edge(&mut self, i: usize, j: usize) { + // 参数 i, j 对应 vertices 元素索引 + // 索引越界与相等处理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + self.adj_mat[i][j] = 1; + self.adj_mat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + pub fn remove_edge(&mut self, i: usize, j: usize) { + // 参数 i, j 对应 vertices 元素索引 + // 索引越界与相等处理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + self.adj_mat[i][j] = 0; + self.adj_mat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + pub fn print(&self) { + println!("顶点列表 = {:?}", self.vertices); + println!("邻接矩阵 ="); + println!("["); + for row in &self.adj_mat { + println!(" {:?},", row); + } + println!("]") + } + } + ``` + +=== "C" + + ```c title="graph_adjacency_matrix.c" + /* 基于邻接矩阵实现的无向图结构体 */ + typedef struct { + int vertices[MAX_SIZE]; + int adjMat[MAX_SIZE][MAX_SIZE]; + int size; + } GraphAdjMat; + + /* 构造函数 */ + GraphAdjMat *newGraphAdjMat() { + GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + for (int j = 0; j < MAX_SIZE; j++) { + graph->adjMat[i][j] = 0; + } + } + return graph; + } + + /* 析构函数 */ + void delGraphAdjMat(GraphAdjMat *graph) { + free(graph); + } + + /* 添加顶点 */ + void addVertex(GraphAdjMat *graph, int val) { + if (graph->size == MAX_SIZE) { + fprintf(stderr, "图的顶点数量已达最大值\n"); + return; + } + // 添加第 n 个顶点,并将第 n 行和列置零 + int n = graph->size; + graph->vertices[n] = val; + for (int i = 0; i <= n; i++) { + graph->adjMat[n][i] = graph->adjMat[i][n] = 0; + } + graph->size++; + } + + /* 删除顶点 */ + void removeVertex(GraphAdjMat *graph, int index) { + if (index < 0 || index >= graph->size) { + fprintf(stderr, "顶点索引越界\n"); + return; + } + // 在顶点列表中移除索引 index 的顶点 + for (int i = index; i < graph->size - 1; i++) { + graph->vertices[i] = graph->vertices[i + 1]; + } + // 在邻接矩阵中删除索引 index 的行 + for (int i = index; i < graph->size - 1; i++) { + for (int j = 0; j < graph->size; j++) { + graph->adjMat[i][j] = graph->adjMat[i + 1][j]; + } + } + // 在邻接矩阵中删除索引 index 的列 + for (int i = 0; i < graph->size; i++) { + for (int j = index; j < graph->size - 1; j++) { + graph->adjMat[i][j] = graph->adjMat[i][j + 1]; + } + } + graph->size--; + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "边索引越界或相等\n"); + return; + } + graph->adjMat[i][j] = 1; + graph->adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "边索引越界或相等\n"); + return; + } + graph->adjMat[i][j] = 0; + graph->adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + void printGraphAdjMat(GraphAdjMat *graph) { + printf("顶点列表 = "); + printArray(graph->vertices, graph->size); + printf("邻接矩阵 =\n"); + for (int i = 0; i < graph->size; i++) { + printArray(graph->adjMat[i], graph->size); + } + } + ``` + +=== "Kotlin" + + ```kotlin title="graph_adjacency_matrix.kt" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat(vertices: IntArray, edges: Array) { + val vertices: MutableList = ArrayList() // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + val adjMat: MutableList> = ArrayList() // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + init { + // 添加顶点 + for (vertex in vertices) { + addVertex(vertex) + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (edge in edges) { + addEdge(edge[0], edge[1]) + } + } + + /* 获取顶点数量 */ + fun size(): Int { + return vertices.size + } + + /* 添加顶点 */ + fun addVertex(value: Int) { + val n = size() + // 向顶点列表中添加新顶点的值 + vertices.add(value) + // 在邻接矩阵中添加一行 + val newRow: MutableList = mutableListOf() + for (j in 0..= size()) throw IndexOutOfBoundsException() + // 在顶点列表中移除索引 index 的顶点 + vertices.removeAt(index) + // 在邻接矩阵中删除索引 index 的行 + adjMat.removeAt(index) + // 在邻接矩阵中删除索引 index 的列 + for (row in adjMat) { + row.removeAt(index) + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + fun addEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw java.lang.IndexOutOfBoundsException() + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + fun removeEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw java.lang.IndexOutOfBoundsException() + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + fun print() { + print("顶点列表 = ") + println(vertices); + println("邻接矩阵 ="); + printMatrix(adjMat) + } + } + ``` + +=== "Ruby" + + ```ruby title="graph_adjacency_matrix.rb" + [class]{GraphAdjMat}-[func]{} + ``` + +=== "Zig" + + ```zig title="graph_adjacency_matrix.zig" + [class]{GraphAdjMat}-[func]{} + ``` + +??? pythontutor "Code Visualization" + +
+ + +## 9.2.2   Implementation based on adjacency list + +Given an undirected graph with a total of $n$ vertices and $m$ edges, the various operations can be implemented as shown in the Figure 9-8 . + +- **Adding an edge**: Simply add the edge at the end of the corresponding vertex's linked list, using $O(1)$ time. Because it is an undirected graph, it is necessary to add edges in both directions simultaneously. +- **Removing an edge**: Find and remove the specified edge in the corresponding vertex's linked list, using $O(m)$ time. In an undirected graph, it is necessary to remove edges in both directions simultaneously. +- **Adding a vertex**: Add a linked list in the adjacency list and make the new vertex the head node of the list, using $O(1)$ time. +- **Removing a vertex**: It is necessary to traverse the entire adjacency list, removing all edges that include the specified vertex, using $O(n + m)$ time. +- **Initialization**: Create $n$ vertices and $2m$ edges in the adjacency list, using $O(n + m)$ time. + +=== "Initialize adjacency list" + ![Initialization, adding and removing edges, adding and removing vertices in adjacency list](graph_operations.assets/adjacency_list_step1_initialization.png){ class="animation-figure" } + +=== "Add an edge" + ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png){ class="animation-figure" } + +=== "Remove an edge" + ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png){ class="animation-figure" } + +=== "Add a vertex" + ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png){ class="animation-figure" } + +=== "Remove a vertex" + ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png){ class="animation-figure" } + +

Figure 9-8   Initialization, adding and removing edges, adding and removing vertices in adjacency list

+ +Below is the adjacency list code implementation. Compared to the above diagram, the actual code has the following differences. + +- For convenience in adding and removing vertices, and to simplify the code, we use lists (dynamic arrays) instead of linked lists. +- Use a hash table to store the adjacency list, `key` being the vertex instance, `value` being the list (linked list) of adjacent vertices of that vertex. + +Additionally, we use the `Vertex` class to represent vertices in the adjacency list. The reason for this is: if, like with the adjacency matrix, list indexes were used to distinguish different vertices, then suppose you want to delete the vertex at index $i$, you would need to traverse the entire adjacency list and decrement all indexes greater than $i$ by $1$, which is very inefficient. However, if each vertex is a unique `Vertex` instance, then deleting a vertex does not require any changes to other vertices. + +=== "Python" + + ```python title="graph_adjacency_list.py" + class GraphAdjList: + """基于邻接表实现的无向图类""" + + def __init__(self, edges: list[list[Vertex]]): + """构造方法""" + # 邻接表,key:顶点,value:该顶点的所有邻接顶点 + self.adj_list = dict[Vertex, list[Vertex]]() + # 添加所有顶点和边 + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + def size(self) -> int: + """获取顶点数量""" + return len(self.adj_list) + + def add_edge(self, vet1: Vertex, vet2: Vertex): + """添加边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 添加边 vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """删除边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 删除边 vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + def add_vertex(self, vet: Vertex): + """添加顶点""" + if vet in self.adj_list: + return + # 在邻接表中添加一个新链表 + self.adj_list[vet] = [] + + def remove_vertex(self, vet: Vertex): + """删除顶点""" + if vet not in self.adj_list: + raise ValueError() + # 在邻接表中删除顶点 vet 对应的链表 + self.adj_list.pop(vet) + # 遍历其他顶点的链表,删除所有包含 vet 的边 + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) + + def print(self): + """打印邻接表""" + print("邻接表 =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + ``` + +=== "C++" + + ```cpp title="graph_adjacency_list.cpp" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + public: + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + unordered_map> adjList; + + /* 在 vector 中删除指定节点 */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* 构造方法 */ + GraphAdjList(const vector> &edges) { + // 添加所有顶点和边 + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() { + return adjList.size(); + } + + /* 添加边 */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在顶点"); + // 添加边 vet1 - vet2 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* 删除边 */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在顶点"); + // 删除边 vet1 - vet2 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* 添加顶点 */ + void addVertex(Vertex *vet) { + if (adjList.count(vet)) + return; + // 在邻接表中添加一个新链表 + adjList[vet] = vector(); + } + + /* 删除顶点 */ + void removeVertex(Vertex *vet) { + if (!adjList.count(vet)) + throw invalid_argument("不存在顶点"); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.erase(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* 打印邻接表 */ + void print() { + cout << "邻接表 =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } + }; + ``` + +=== "Java" + + ```java title="graph_adjacency_list.java" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + Map> adjList; + + /* 构造方法 */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // 添加所有顶点和边 + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + public int size() { + return adjList.size(); + } + + /* 添加边 */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 添加边 vet1 - vet2 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* 删除边 */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 删除边 vet1 - vet2 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* 添加顶点 */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // 在邻接表中添加一个新链表 + adjList.put(vet, new ArrayList<>()); + } + + /* 删除顶点 */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (List list : adjList.values()) { + list.remove(vet); + } + } + + /* 打印邻接表 */ + public void print() { + System.out.println("邻接表 ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } + } + ``` + +=== "C#" + + ```csharp title="graph_adjacency_list.cs" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + public Dictionary> adjList; + + /* 构造函数 */ + public GraphAdjList(Vertex[][] edges) { + adjList = []; + // 添加所有顶点和边 + foreach (Vertex[] edge in edges) { + AddVertex(edge[0]); + AddVertex(edge[1]); + AddEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int Size() { + return adjList.Count; + } + + /* 添加边 */ + public void AddEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 添加边 vet1 - vet2 + adjList[vet1].Add(vet2); + adjList[vet2].Add(vet1); + } + + /* 删除边 */ + public void RemoveEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 删除边 vet1 - vet2 + adjList[vet1].Remove(vet2); + adjList[vet2].Remove(vet1); + } + + /* 添加顶点 */ + public void AddVertex(Vertex vet) { + if (adjList.ContainsKey(vet)) + return; + // 在邻接表中添加一个新链表 + adjList.Add(vet, []); + } + + /* 删除顶点 */ + public void RemoveVertex(Vertex vet) { + if (!adjList.ContainsKey(vet)) + throw new InvalidOperationException(); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.Remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + foreach (List list in adjList.Values) { + list.Remove(vet); + } + } + + /* 打印邻接表 */ + public void Print() { + Console.WriteLine("邻接表 ="); + foreach (KeyValuePair> pair in adjList) { + List tmp = []; + foreach (Vertex vertex in pair.Value) + tmp.Add(vertex.val); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); + } + } + } + ``` + +=== "Go" + + ```go title="graph_adjacency_list.go" + /* 基于邻接表实现的无向图类 */ + type graphAdjList struct { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + adjList map[Vertex][]Vertex + } + + /* 构造函数 */ + func newGraphAdjList(edges [][]Vertex) *graphAdjList { + g := &graphAdjList{ + adjList: make(map[Vertex][]Vertex), + } + // 添加所有顶点和边 + for _, edge := range edges { + g.addVertex(edge[0]) + g.addVertex(edge[1]) + g.addEdge(edge[0], edge[1]) + } + return g + } + + /* 获取顶点数量 */ + func (g *graphAdjList) size() int { + return len(g.adjList) + } + + /* 添加边 */ + func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 添加边 vet1 - vet2, 添加匿名 struct{}, + g.adjList[vet1] = append(g.adjList[vet1], vet2) + g.adjList[vet2] = append(g.adjList[vet2], vet1) + } + + /* 删除边 */ + func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 删除边 vet1 - vet2 + g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) + g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) + } + + /* 添加顶点 */ + func (g *graphAdjList) addVertex(vet Vertex) { + _, ok := g.adjList[vet] + if ok { + return + } + // 在邻接表中添加一个新链表 + g.adjList[vet] = make([]Vertex, 0) + } + + /* 删除顶点 */ + func (g *graphAdjList) removeVertex(vet Vertex) { + _, ok := g.adjList[vet] + if !ok { + panic("error") + } + // 在邻接表中删除顶点 vet 对应的链表 + delete(g.adjList, vet) + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for v, list := range g.adjList { + g.adjList[v] = DeleteSliceElms(list, vet) + } + } + + /* 打印邻接表 */ + func (g *graphAdjList) print() { + var builder strings.Builder + fmt.Printf("邻接表 = \n") + for k, v := range g.adjList { + builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") + for _, vet := range v { + builder.WriteString(strconv.Itoa(vet.Val) + " ") + } + fmt.Println(builder.String()) + builder.Reset() + } + } + ``` + +=== "Swift" + + ```swift title="graph_adjacency_list.swift" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + public private(set) var adjList: [Vertex: [Vertex]] + + /* 构造方法 */ + public init(edges: [[Vertex]]) { + adjList = [:] + // 添加所有顶点和边 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 获取顶点数量 */ + public func size() -> Int { + adjList.count + } + + /* 添加边 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("参数错误") + } + // 添加边 vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 删除边 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("参数错误") + } + // 删除边 vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 添加顶点 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 在邻接表中添加一个新链表 + adjList[vet] = [] + } + + /* 删除顶点 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("参数错误") + } + // 在邻接表中删除顶点 vet 对应的链表 + adjList.removeValue(forKey: vet) + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 打印邻接表 */ + public func print() { + Swift.print("邻接表 =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } + } + ``` + +=== "JS" + + ```javascript title="graph_adjacency_list.js" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + adjList; + + /* 构造方法 */ + constructor(edges) { + this.adjList = new Map(); + // 添加所有顶点和边 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + size() { + return this.adjList.size; + } + + /* 添加边 */ + addEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 添加边 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 删除边 */ + removeEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 删除边 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 添加顶点 */ + addVertex(vet) { + if (this.adjList.has(vet)) return; + // 在邻接表中添加一个新链表 + this.adjList.set(vet, []); + } + + /* 删除顶点 */ + removeVertex(vet) { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在邻接表中删除顶点 vet 对应的链表 + this.adjList.delete(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (const set of this.adjList.values()) { + const index = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 打印邻接表 */ + print() { + console.log('邻接表 ='); + for (const [key, value] of this.adjList) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } + } + ``` + +=== "TS" + + ```typescript title="graph_adjacency_list.ts" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + adjList: Map; + + /* 构造方法 */ + constructor(edges: Vertex[][]) { + this.adjList = new Map(); + // 添加所有顶点和边 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + size(): number { + return this.adjList.size; + } + + /* 添加边 */ + addEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 添加边 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 删除边 */ + removeEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 删除边 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 添加顶点 */ + addVertex(vet: Vertex): void { + if (this.adjList.has(vet)) return; + // 在邻接表中添加一个新链表 + this.adjList.set(vet, []); + } + + /* 删除顶点 */ + removeVertex(vet: Vertex): void { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在邻接表中删除顶点 vet 对应的链表 + this.adjList.delete(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (const set of this.adjList.values()) { + const index: number = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 打印邻接表 */ + print(): void { + console.log('邻接表 ='); + for (const [key, value] of this.adjList.entries()) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } + } + ``` + +=== "Dart" + + ```dart title="graph_adjacency_list.dart" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + Map> adjList = {}; + + /* 构造方法 */ + GraphAdjList(List> edges) { + for (List edge in edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() { + return adjList.length; + } + + /* 添加边 */ + void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 添加边 vet1 - vet2 + adjList[vet1]!.add(vet2); + adjList[vet2]!.add(vet1); + } + + /* 删除边 */ + void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 删除边 vet1 - vet2 + adjList[vet1]!.remove(vet2); + adjList[vet2]!.remove(vet1); + } + + /* 添加顶点 */ + void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) return; + // 在邻接表中添加一个新链表 + adjList[vet] = []; + } + + /* 删除顶点 */ + void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) { + throw ArgumentError; + } + // 在邻接表中删除顶点 vet 对应的链表 + adjList.remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + adjList.forEach((key, value) { + value.remove(vet); + }); + } + + /* 打印邻接表 */ + void printAdjList() { + print("邻接表 ="); + adjList.forEach((key, value) { + List tmp = []; + for (Vertex vertex in value) { + tmp.add(vertex.val); + } + print("${key.val}: $tmp,"); + }); + } + } + ``` + +=== "Rust" + + ```rust title="graph_adjacency_list.rs" + /* 基于邻接表实现的无向图类型 */ + pub struct GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + pub adj_list: HashMap>, + } + + impl GraphAdjList { + /* 构造方法 */ + pub fn new(edges: Vec<[Vertex; 2]>) -> Self { + let mut graph = GraphAdjList { + adj_list: HashMap::new(), + }; + // 添加所有顶点和边 + for edge in edges { + graph.add_vertex(edge[0]); + graph.add_vertex(edge[1]); + graph.add_edge(edge[0], edge[1]); + } + + graph + } + + /* 获取顶点数量 */ + #[allow(unused)] + pub fn size(&self) -> usize { + self.adj_list.len() + } + + /* 添加边 */ + pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 添加边 vet1 - vet2 + self.adj_list.get_mut(&vet1).unwrap().push(vet2); + self.adj_list.get_mut(&vet2).unwrap().push(vet1); + } + + /* 删除边 */ + #[allow(unused)] + pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 删除边 vet1 - vet2 + self.adj_list + .get_mut(&vet1) + .unwrap() + .retain(|&vet| vet != vet2); + self.adj_list + .get_mut(&vet2) + .unwrap() + .retain(|&vet| vet != vet1); + } + + /* 添加顶点 */ + pub fn add_vertex(&mut self, vet: Vertex) { + if self.adj_list.contains_key(&vet) { + return; + } + // 在邻接表中添加一个新链表 + self.adj_list.insert(vet, vec![]); + } + + /* 删除顶点 */ + #[allow(unused)] + pub fn remove_vertex(&mut self, vet: Vertex) { + if !self.adj_list.contains_key(&vet) { + panic!("value error"); + } + // 在邻接表中删除顶点 vet 对应的链表 + self.adj_list.remove(&vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for list in self.adj_list.values_mut() { + list.retain(|&v| v != vet); + } + } + + /* 打印邻接表 */ + pub fn print(&self) { + println!("邻接表 ="); + for (vertex, list) in &self.adj_list { + let list = list.iter().map(|vertex| vertex.val).collect::>(); + println!("{}: {:?},", vertex.val, list); + } + } + } + ``` + +=== "C" + + ```c title="graph_adjacency_list.c" + /* 节点结构体 */ + typedef struct AdjListNode { + Vertex *vertex; // 顶点 + struct AdjListNode *next; // 后继节点 + } AdjListNode; + + /* 查找顶点对应的节点 */ + AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { + for (int i = 0; i < graph->size; i++) { + if (graph->heads[i]->vertex == vet) { + return graph->heads[i]; + } + } + return NULL; + } + + /* 添加边辅助函数 */ + void addEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); + node->vertex = vet; + // 头插法 + node->next = head->next; + head->next = node; + } + + /* 删除边辅助函数 */ + void removeEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *pre = head; + AdjListNode *cur = head->next; + // 在链表中搜索 vet 对应节点 + while (cur != NULL && cur->vertex != vet) { + pre = cur; + cur = cur->next; + } + if (cur == NULL) + return; + // 将 vet 对应节点从链表中删除 + pre->next = cur->next; + // 释放内存 + free(cur); + } + + /* 基于邻接表实现的无向图类 */ + typedef struct { + AdjListNode *heads[MAX_SIZE]; // 节点数组 + int size; // 节点数量 + } GraphAdjList; + + /* 构造函数 */ + GraphAdjList *newGraphAdjList() { + GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); + if (!graph) { + return NULL; + } + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + graph->heads[i] = NULL; + } + return graph; + } + + /* 析构函数 */ + void delGraphAdjList(GraphAdjList *graph) { + for (int i = 0; i < graph->size; i++) { + AdjListNode *cur = graph->heads[i]; + while (cur != NULL) { + AdjListNode *next = cur->next; + if (cur != graph->heads[i]) { + free(cur); + } + cur = next; + } + free(graph->heads[i]->vertex); + free(graph->heads[i]); + } + free(graph); + } + + /* 查找顶点对应的节点 */ + AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { + for (int i = 0; i < graph->size; i++) { + if (graph->heads[i]->vertex == vet) { + return graph->heads[i]; + } + } + return NULL; + } + + /* 添加边 */ + void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL && head1 != head2); + // 添加边 vet1 - vet2 + addEdgeHelper(head1, vet2); + addEdgeHelper(head2, vet1); + } + + /* 删除边 */ + void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL); + // 删除边 vet1 - vet2 + removeEdgeHelper(head1, head2->vertex); + removeEdgeHelper(head2, head1->vertex); + } + + /* 添加顶点 */ + void addVertex(GraphAdjList *graph, Vertex *vet) { + assert(graph != NULL && graph->size < MAX_SIZE); + AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); + head->vertex = vet; + head->next = NULL; + // 在邻接表中添加一个新链表 + graph->heads[graph->size++] = head; + } + + /* 删除顶点 */ + void removeVertex(GraphAdjList *graph, Vertex *vet) { + AdjListNode *node = findNode(graph, vet); + assert(node != NULL); + // 在邻接表中删除顶点 vet 对应的链表 + AdjListNode *cur = node, *pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + free(pre); + } + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (int i = 0; i < graph->size; i++) { + cur = graph->heads[i]; + pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + if (cur && cur->vertex == vet) { + pre->next = cur->next; + free(cur); + break; + } + } + } + // 将该顶点之后的顶点向前移动,以填补空缺 + int i; + for (i = 0; i < graph->size; i++) { + if (graph->heads[i] == node) + break; + } + for (int j = i; j < graph->size - 1; j++) { + graph->heads[j] = graph->heads[j + 1]; + } + graph->size--; + free(vet); + } + ``` + +=== "Kotlin" + + ```kotlin title="graph_adjacency_list.kt" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList(edges: Array>) { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + val adjList: MutableMap> = HashMap() + + /* 构造函数 */ + init { + // 添加所有顶点和边 + for (edge in edges) { + addVertex(edge[0]!!); + addVertex(edge[1]!!); + addEdge(edge[0]!!, edge[1]!!); + } + } + + /* 获取顶点数量 */ + fun size(): Int { + return adjList.size + } + + /* 添加边 */ + fun addEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 添加边 vet1 - vet2 + adjList[vet1]?.add(vet2) + adjList[vet2]?.add(vet1); + } + + /* 删除边 */ + fun removeEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 删除边 vet1 - vet2 + adjList[vet1]?.remove(vet2); + adjList[vet2]?.remove(vet1); + } + + /* 添加顶点 */ + fun addVertex(vet: Vertex) { + if (adjList.containsKey(vet)) + return + // 在邻接表中添加一个新链表 + adjList[vet] = mutableListOf() + } + + /* 删除顶点 */ + fun removeVertex(vet: Vertex) { + if (!adjList.containsKey(vet)) + throw IllegalArgumentException() + // 在邻接表中删除顶点 vet 对应的链表 + adjList.remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (list in adjList.values) { + list.remove(vet) + } + } + + /* 打印邻接表 */ + fun print() { + println("邻接表 =") + for (pair in adjList.entries) { + val tmp = ArrayList() + for (vertex in pair.value) { + tmp.add(vertex.value) + } + println("${pair.key.value}: $tmp,") + } + } + } + ``` + +=== "Ruby" + + ```ruby title="graph_adjacency_list.rb" + [class]{GraphAdjList}-[func]{} + ``` + +=== "Zig" + + ```zig title="graph_adjacency_list.zig" + [class]{GraphAdjList}-[func]{} + ``` + +??? pythontutor "Code Visualization" + +
+ + +## 9.2.3   Efficiency comparison + +Assuming there are $n$ vertices and $m$ edges in the graph, the Table 9-2 compares the time efficiency and space efficiency of the adjacency matrix and adjacency list. + +

Table 9-2   Comparison of adjacency matrix and adjacency list

+ +
+ +| | Adjacency matrix | Adjacency list (Linked list) | Adjacency list (Hash table) | +| ------------------- | ---------------- | ---------------------------- | --------------------------- | +| Determine adjacency | $O(1)$ | $O(m)$ | $O(1)$ | +| Add an edge | $O(1)$ | $O(1)$ | $O(1)$ | +| Remove an edge | $O(1)$ | $O(m)$ | $O(1)$ | +| Add a vertex | $O(n)$ | $O(1)$ | $O(1)$ | +| Remove a vertex | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| Memory space usage | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | + +
+ +Observing the Table 9-2 , it seems that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, the adjacency matrix exemplifies the principle of "space for time", while the adjacency list exemplifies "time for space". diff --git a/en/docs/chapter_graph/graph_traversal.md b/en/docs/chapter_graph/graph_traversal.md new file mode 100644 index 000000000..8f83afc6d --- /dev/null +++ b/en/docs/chapter_graph/graph_traversal.md @@ -0,0 +1,967 @@ +--- +comments: true +--- + +# 9.3   Graph traversal + +Trees represent a "one-to-many" relationship, while graphs have a higher degree of freedom and can represent any "many-to-many" relationship. Therefore, we can consider trees as a special case of graphs. Clearly, **tree traversal operations are also a special case of graph traversal operations**. + +Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal can be divided into two types: "Breadth-First Search (BFS)" and "Depth-First Search (DFS)". + +## 9.3.1   Breadth-first search + +**Breadth-first search is a near-to-far traversal method, starting from a certain node, always prioritizing the visit to the nearest vertices and expanding outwards layer by layer**. As shown in the Figure 9-9 , starting from the top left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited. + +![Breadth-first traversal of a graph](graph_traversal.assets/graph_bfs.png){ class="animation-figure" } + +

Figure 9-9   Breadth-first traversal of a graph

+ +### 1.   Algorithm implementation + +BFS is usually implemented with the help of a queue, as shown in the code below. The queue has a "first in, first out" property, which aligns with the BFS idea of traversing "from near to far". + +1. Add the starting vertex `startVet` to the queue and start the loop. +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. + +=== "Python" + + ```python title="graph_bfs.py" + def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """广度优先遍历""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set[Vertex]([start_vet]) + # 队列用于实现 BFS + que = deque[Vertex]([start_vet]) + # 以顶点 vet 为起点,循环直至访问完所有顶点 + while len(que) > 0: + vet = que.popleft() # 队首顶点出队 + res.append(vet) # 记录访问顶点 + # 遍历该顶点的所有邻接顶点 + for adj_vet in graph.adj_list[vet]: + if adj_vet in visited: + continue # 跳过已被访问的顶点 + que.append(adj_vet) # 只入队未访问的顶点 + visited.add(adj_vet) # 标记该顶点已被访问 + # 返回顶点遍历序列 + return res + ``` + +=== "C++" + + ```cpp title="graph_bfs.cpp" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // 顶点遍历序列 + vector res; + // 哈希表,用于记录已被访问过的顶点 + unordered_set visited = {startVet}; + // 队列用于实现 BFS + queue que; + que.push(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // 队首顶点出队 + res.push_back(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问的顶点 + que.push(adjVet); // 只入队未访问的顶点 + visited.emplace(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; + } + ``` + +=== "Java" + + ```java title="graph_bfs.java" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List graphBFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new ArrayList<>(); + // 哈希表,用于记录已被访问过的顶点 + Set visited = new HashSet<>(); + visited.add(startVet); + // 队列用于实现 BFS + Queue que = new LinkedList<>(); + que.offer(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.isEmpty()) { + Vertex vet = que.poll(); // 队首顶点出队 + res.add(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳过已被访问的顶点 + que.offer(adjVet); // 只入队未访问的顶点 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; + } + ``` + +=== "C#" + + ```csharp title="graph_bfs.cs" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List GraphBFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = []; + // 哈希表,用于记录已被访问过的顶点 + HashSet visited = [startVet]; + // 队列用于实现 BFS + Queue que = new(); + que.Enqueue(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.Count > 0) { + Vertex vet = que.Dequeue(); // 队首顶点出队 + res.Add(vet); // 记录访问顶点 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.Enqueue(adjVet); // 只入队未访问的顶点 + visited.Add(adjVet); // 标记该顶点已被访问 + } + } + + // 返回顶点遍历序列 + return res; + } + ``` + +=== "Go" + + ```go title="graph_bfs.go" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { + // 顶点遍历序列 + res := make([]Vertex, 0) + // 哈希表,用于记录已被访问过的顶点 + visited := make(map[Vertex]struct{}) + visited[startVet] = struct{}{} + // 队列用于实现 BFS, 使用切片模拟队列 + queue := make([]Vertex, 0) + queue = append(queue, startVet) + // 以顶点 vet 为起点,循环直至访问完所有顶点 + for len(queue) > 0 { + // 队首顶点出队 + vet := queue[0] + queue = queue[1:] + // 记录访问顶点 + res = append(res, vet) + // 遍历该顶点的所有邻接顶点 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 只入队未访问的顶点 + if !isExist { + queue = append(queue, adjVet) + visited[adjVet] = struct{}{} + } + } + } + // 返回顶点遍历序列 + return res + } + ``` + +=== "Swift" + + ```swift title="graph_bfs.swift" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希表,用于记录已被访问过的顶点 + var visited: Set = [startVet] + // 队列用于实现 BFS + var que: [Vertex] = [startVet] + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while !que.isEmpty { + let vet = que.removeFirst() // 队首顶点出队 + res.append(vet) // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问的顶点 + } + que.append(adjVet) // 只入队未访问的顶点 + visited.insert(adjVet) // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res + } + ``` + +=== "JS" + + ```javascript title="graph_bfs.js" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + function graphBFS(graph, startVet) { + // 顶点遍历序列 + const res = []; + // 哈希表,用于记录已被访问过的顶点 + const visited = new Set(); + visited.add(startVet); + // 队列用于实现 BFS + const que = [startVet]; + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.length) { + const vet = que.shift(); // 队首顶点出队 + res.push(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.push(adjVet); // 只入队未访问的顶点 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; + } + ``` + +=== "TS" + + ```typescript title="graph_bfs.ts" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 顶点遍历序列 + const res: Vertex[] = []; + // 哈希表,用于记录已被访问过的顶点 + const visited: Set = new Set(); + visited.add(startVet); + // 队列用于实现 BFS + const que = [startVet]; + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.length) { + const vet = que.shift(); // 队首顶点出队 + res.push(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.push(adjVet); // 只入队未访问 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; + } + ``` + +=== "Dart" + + ```dart title="graph_bfs.dart" + /* 广度优先遍历 */ + List graphBFS(GraphAdjList graph, Vertex startVet) { + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + // 顶点遍历序列 + List res = []; + // 哈希表,用于记录已被访问过的顶点 + Set visited = {}; + visited.add(startVet); + // 队列用于实现 BFS + Queue que = Queue(); + que.add(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.isNotEmpty) { + Vertex vet = que.removeFirst(); // 队首顶点出队 + res.add(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.add(adjVet); // 只入队未访问的顶点 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; + } + ``` + +=== "Rust" + + ```rust title="graph_bfs.rs" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 顶点遍历序列 + let mut res = vec![]; + // 哈希表,用于记录已被访问过的顶点 + let mut visited = HashSet::new(); + visited.insert(start_vet); + // 队列用于实现 BFS + let mut que = VecDeque::new(); + que.push_back(start_vet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while !que.is_empty() { + let vet = que.pop_front().unwrap(); // 队首顶点出队 + res.push(vet); // 记录访问顶点 + + // 遍历该顶点的所有邻接顶点 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳过已被访问的顶点 + } + que.push_back(adj_vet); // 只入队未访问的顶点 + visited.insert(adj_vet); // 标记该顶点已被访问 + } + } + } + // 返回顶点遍历序列 + res + } + ``` + +=== "C" + + ```c title="graph_bfs.c" + /* 节点队列结构体 */ + typedef struct { + Vertex *vertices[MAX_SIZE]; + int front, rear, size; + } Queue; + + /* 构造函数 */ + Queue *newQueue() { + Queue *q = (Queue *)malloc(sizeof(Queue)); + q->front = q->rear = q->size = 0; + return q; + } + + /* 判断队列是否为空 */ + int isEmpty(Queue *q) { + return q->size == 0; + } + + /* 入队操作 */ + void enqueue(Queue *q, Vertex *vet) { + q->vertices[q->rear] = vet; + q->rear = (q->rear + 1) % MAX_SIZE; + q->size++; + } + + /* 出队操作 */ + Vertex *dequeue(Queue *q) { + Vertex *vet = q->vertices[q->front]; + q->front = (q->front + 1) % MAX_SIZE; + q->size--; + return vet; + } + + /* 检查顶点是否已被访问 */ + int isVisited(Vertex **visited, int size, Vertex *vet) { + // 遍历查找节点,使用 O(n) 时间 + for (int i = 0; i < size; i++) { + if (visited[i] == vet) + return 1; + } + return 0; + } + + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { + // 队列用于实现 BFS + Queue *queue = newQueue(); + enqueue(queue, startVet); + visited[(*visitedSize)++] = startVet; + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!isEmpty(queue)) { + Vertex *vet = dequeue(queue); // 队首顶点出队 + res[(*resSize)++] = vet; // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳过已被访问的顶点 + if (!isVisited(visited, *visitedSize, node->vertex)) { + enqueue(queue, node->vertex); // 只入队未访问的顶点 + visited[(*visitedSize)++] = node->vertex; // 标记该顶点已被访问 + } + node = node->next; + } + } + // 释放内存 + free(queue); + } + ``` + +=== "Kotlin" + + ```kotlin title="graph_bfs.kt" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + fun graphBFS(graph: GraphAdjList, startVet: Vertex): List { + // 顶点遍历序列 + val res: MutableList = ArrayList() + // 哈希表,用于记录已被访问过的顶点 + val visited: MutableSet = HashSet() + visited.add(startVet) + // 队列用于实现 BFS + val que: Queue = LinkedList() + que.offer(startVet) + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.isEmpty()) { + val vet = que.poll() // 队首顶点出队 + res.add(vet) // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) continue // 跳过已被访问的顶点 + + que.offer(adjVet) // 只入队未访问的顶点 + visited.add(adjVet) // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res + } + ``` + +=== "Ruby" + + ```ruby title="graph_bfs.rb" + [class]{}-[func]{graph_bfs} + ``` + +=== "Zig" + + ```zig title="graph_bfs.zig" + [class]{}-[func]{graphBFS} + ``` + +??? pythontutor "Code Visualization" + +
+ + +The code is relatively abstract, it is suggested to compare with the following figure to deepen the understanding. + +=== "<1>" + ![Steps of breadth-first search of a graph](graph_traversal.assets/graph_bfs_step1.png){ class="animation-figure" } + +=== "<2>" + ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png){ class="animation-figure" } + +=== "<3>" + ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png){ class="animation-figure" } + +=== "<4>" + ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png){ class="animation-figure" } + +=== "<5>" + ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png){ class="animation-figure" } + +=== "<6>" + ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png){ class="animation-figure" } + +=== "<7>" + ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png){ class="animation-figure" } + +=== "<8>" + ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png){ class="animation-figure" } + +=== "<9>" + ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png){ class="animation-figure" } + +=== "<10>" + ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png){ class="animation-figure" } + +=== "<11>" + ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png){ class="animation-figure" } + +

Figure 9-10   Steps of breadth-first search of a graph

+ +!!! question "Is the sequence of breadth-first traversal unique?" + + Not unique. Breadth-first traversal only requires traversing in a "from near to far" order, **and the traversal order of multiple vertices at the same distance can be arbitrarily shuffled**. For example, in the above figure, the visitation order of vertices $1$ and $3$ can be switched, as can the order of vertices $2$, $4$, and $6$. + +### 2.   Complexity analysis + +**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. + +## 9.3.2   Depth-first search + +**Depth-first search is a traversal method that prioritizes going as far as possible and then backtracks when no further paths are available**. As shown in the Figure 9-11 , starting from the top left vertex, visit some adjacent vertex of the current vertex until no further path is available, then return and continue until all vertices are traversed. + +![Depth-first traversal of a graph](graph_traversal.assets/graph_dfs.png){ class="animation-figure" } + +

Figure 9-11   Depth-first traversal of a graph

+ +### 1.   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. + +=== "Python" + + ```python title="graph_dfs.py" + def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """深度优先遍历辅助函数""" + res.append(vet) # 记录访问顶点 + visited.add(vet) # 标记该顶点已被访问 + # 遍历该顶点的所有邻接顶点 + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # 跳过已被访问的顶点 + # 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) + + def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """深度优先遍历""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set[Vertex]() + dfs(graph, visited, res, start_vet) + return res + ``` + +=== "C++" + + ```cpp title="graph_dfs.cpp" + /* 深度优先遍历辅助函数 */ + void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { + res.push_back(vet); // 记录访问顶点 + visited.emplace(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // 顶点遍历序列 + vector res; + // 哈希表,用于记录已被访问过的顶点 + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; + } + ``` + +=== "Java" + + ```java title="graph_dfs.java" + /* 深度优先遍历辅助函数 */ + void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳过已被访问的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List graphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new ArrayList<>(); + // 哈希表,用于记录已被访问过的顶点 + Set visited = new HashSet<>(); + dfs(graph, visited, res, startVet); + return res; + } + ``` + +=== "C#" + + ```csharp title="graph_dfs.cs" + /* 深度优先遍历辅助函数 */ + void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { + res.Add(vet); // 记录访问顶点 + visited.Add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + DFS(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List GraphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = []; + // 哈希表,用于记录已被访问过的顶点 + HashSet visited = []; + DFS(graph, visited, res, startVet); + return res; + } + ``` + +=== "Go" + + ```go title="graph_dfs.go" + /* 深度优先遍历辅助函数 */ + func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { + // append 操作会返回新的的引用,必须让原引用重新赋值为新slice的引用 + *res = append(*res, vet) + visited[vet] = struct{}{} + // 遍历该顶点的所有邻接顶点 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 递归访问邻接顶点 + if !isExist { + dfs(g, visited, res, adjVet) + } + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { + // 顶点遍历序列 + res := make([]Vertex, 0) + // 哈希表,用于记录已被访问过的顶点 + visited := make(map[Vertex]struct{}) + dfs(g, visited, &res, startVet) + // 返回顶点遍历序列 + return res + } + ``` + +=== "Swift" + + ```swift title="graph_dfs.swift" + /* 深度优先遍历辅助函数 */ + func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // 记录访问顶点 + visited.insert(vet) // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希表,用于记录已被访问过的顶点 + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res + } + ``` + +=== "JS" + + ```javascript title="graph_dfs.js" + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + function dfs(graph, visited, res, vet) { + res.push(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + function graphDFS(graph, startVet) { + // 顶点遍历序列 + const res = []; + // 哈希表,用于记录已被访问过的顶点 + const visited = new Set(); + dfs(graph, visited, res, startVet); + return res; + } + ``` + +=== "TS" + + ```typescript title="graph_dfs.ts" + /* 深度优先遍历辅助函数 */ + function dfs( + graph: GraphAdjList, + visited: Set, + res: Vertex[], + vet: Vertex + ): void { + res.push(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 顶点遍历序列 + const res: Vertex[] = []; + // 哈希表,用于记录已被访问过的顶点 + const visited: Set = new Set(); + dfs(graph, visited, res, startVet); + return res; + } + ``` + +=== "Dart" + + ```dart title="graph_dfs.dart" + /* 深度优先遍历辅助函数 */ + void dfs( + GraphAdjList graph, + Set visited, + List res, + Vertex vet, + ) { + res.add(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + List graphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = []; + // 哈希表,用于记录已被访问过的顶点 + Set visited = {}; + dfs(graph, visited, res, startVet); + return res; + } + ``` + +=== "Rust" + + ```rust title="graph_dfs.rs" + /* 深度优先遍历辅助函数 */ + fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { + res.push(vet); // 记录访问顶点 + visited.insert(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adj_vet); + } + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 顶点遍历序列 + let mut res = vec![]; + // 哈希表,用于记录已被访问过的顶点 + let mut visited = HashSet::new(); + dfs(&graph, &mut visited, &mut res, start_vet); + + res + } + ``` + +=== "C" + + ```c title="graph_dfs.c" + /* 检查顶点是否已被访问 */ + int isVisited(Vertex **res, int size, Vertex *vet) { + // 遍历查找节点,使用 O(n) 时间 + for (int i = 0; i < size; i++) { + if (res[i] == vet) { + return 1; + } + } + return 0; + } + + /* 深度优先遍历辅助函数 */ + void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { + // 记录访问顶点 + res[(*resSize)++] = vet; + // 遍历该顶点的所有邻接顶点 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳过已被访问的顶点 + if (!isVisited(res, *resSize, node->vertex)) { + // 递归访问邻接顶点 + dfs(graph, res, resSize, node->vertex); + } + node = node->next; + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { + dfs(graph, res, resSize, startVet); + } + ``` + +=== "Kotlin" + + ```kotlin title="graph_dfs.kt" + /* 深度优先遍历辅助函数 */ + fun dfs( + graph: GraphAdjList, + visited: MutableSet, + res: MutableList, + vet: Vertex? + ) { + res.add(vet) // 记录访问顶点 + visited.add(vet) // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) continue // 跳过已被访问的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + fun graphDFS( + graph: GraphAdjList, + startVet: Vertex? + ): List { + // 顶点遍历序列 + val res: MutableList = ArrayList() + // 哈希表,用于记录已被访问过的顶点 + val visited: MutableSet = HashSet() + dfs(graph, visited, res, startVet) + return res + } + ``` + +=== "Ruby" + + ```ruby title="graph_dfs.rb" + [class]{}-[func]{dfs} + + [class]{}-[func]{graph_dfs} + ``` + +=== "Zig" + + ```zig title="graph_dfs.zig" + [class]{}-[func]{dfs} + + [class]{}-[func]{graphDFS} + ``` + +??? pythontutor "Code Visualization" + +
+ + +The algorithm process of depth-first search is shown in the following figure. + +- **Dashed lines represent downward recursion**, indicating that a new recursive method has been initiated to visit a new vertex. +- **Curved dashed lines represent upward backtracking**, indicating that this recursive method has returned to the position where this method was initiated. + +To deepen the understanding, it is suggested to combine the following figure with the code to simulate (or draw) the entire DFS process in your mind, including when each recursive method is initiated and when it returns. + +=== "<1>" + ![Steps of depth-first search of a graph](graph_traversal.assets/graph_dfs_step1.png){ class="animation-figure" } + +=== "<2>" + ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png){ class="animation-figure" } + +=== "<3>" + ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png){ class="animation-figure" } + +=== "<4>" + ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png){ class="animation-figure" } + +=== "<5>" + ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png){ class="animation-figure" } + +=== "<6>" + ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png){ class="animation-figure" } + +=== "<7>" + ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png){ class="animation-figure" } + +=== "<8>" + ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png){ class="animation-figure" } + +=== "<9>" + ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png){ class="animation-figure" } + +=== "<10>" + ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png){ class="animation-figure" } + +=== "<11>" + ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png){ class="animation-figure" } + +

Figure 9-12   Steps of depth-first search of a graph

+ +!!! question "Is the sequence of depth-first traversal unique?" + + Similar to breadth-first traversal, the order of the depth-first traversal sequence is also not unique. Given a certain vertex, exploring in any direction first is possible, that is, the order of adjacent vertices can be arbitrarily shuffled, all being part of depth-first traversal. + + Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to preorder, inorder, and postorder traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal. + +### 2.   Complexity analysis + +**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. diff --git a/en/docs/chapter_graph/index.md b/en/docs/chapter_graph/index.md new file mode 100644 index 000000000..095d4b309 --- /dev/null +++ b/en/docs/chapter_graph/index.md @@ -0,0 +1,21 @@ +--- +comments: true +icon: material/graphql +--- + +# Chapter 9.   Graph + +![Graph](../assets/covers/chapter_graph.jpg){ class="cover-image" } + +!!! abstract + + In the journey of life, we are like individual nodes, connected by countless invisible edges. + + Every encountering and parting leaves a unique mark on this vast network graph. + +## Chapter Contents + +- [9.1   Graph](https://www.hello-algo.com/en/chapter_graph/graph/) +- [9.2   Basic Graph Operations](https://www.hello-algo.com/en/chapter_graph/graph_operations/) +- [9.3   Graph Traversal](https://www.hello-algo.com/en/chapter_graph/graph_traversal/) +- [9.4   Summary](https://www.hello-algo.com/en/chapter_graph/summary/) diff --git a/en/docs/chapter_graph/summary.md b/en/docs/chapter_graph/summary.md new file mode 100644 index 000000000..65af81c80 --- /dev/null +++ b/en/docs/chapter_graph/summary.md @@ -0,0 +1,35 @@ +--- +comments: true +--- + +# 9.4   Summary + +### 1.   Key review + +- A graph consists of vertices and edges and can be represented as a set comprising a group of vertices and a group of edges. +- Compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex. +- The edges of a directed graph have directionality, any vertex in a connected graph is reachable, and each edge in a weighted graph contains a weight variable. +- Adjacency matrices use matrices to represent graphs, with each row (column) representing a vertex and matrix elements representing edges, using $1$ or $0$ to indicate the presence or absence of an edge between two vertices. Adjacency matrices are highly efficient for add, delete, find, and modify operations, but they consume more space. +- Adjacency lists use multiple linked lists to represent graphs, with the $i^{th}$ list corresponding to vertex $i$, containing all its adjacent vertices. Adjacency lists save more space compared to adjacency matrices, but since it is necessary to traverse the list to find edges, their time efficiency is lower. +- When the linked lists in the adjacency list are too long, they can be converted into red-black trees or hash tables to improve query efficiency. +- From the perspective of algorithmic thinking, adjacency matrices embody the principle of "space for time," while adjacency lists embody "time for space." +- Graphs can be used to model various real systems, such as social networks, subway routes, etc. +- A tree is a special case of a graph, and tree traversal is also a special case of graph traversal. +- Breadth-first traversal of a graph is a search method that expands layer by layer from near to far, usually implemented with a queue. +- Depth-first traversal of a graph is a search method that prefers to go as deep as possible and backtracks when no further paths are available, often based on recursion. + +### 2.   Q & A + +**Q**: Is a path defined as a sequence of vertices or a sequence of edges? + +Definitions vary between different language versions on Wikipedia: the English version defines a path as "a sequence of edges," while the Chinese version defines it as "a sequence of vertices." Here is the original text from the English version: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. + +In this document, a path is considered a sequence of edges, rather than a sequence of vertices. This is because there might be multiple edges connecting two vertices, in which case each edge corresponds to a path. + +**Q**: In a disconnected graph, are there points that cannot be traversed to? + +In a disconnected graph, starting from a certain vertex, there is at least one vertex that cannot be reached. Traversing a disconnected graph requires setting multiple starting points to traverse all connected components of the graph. + +**Q**: In an adjacency list, does the order of "all vertices connected to that vertex" matter? + +It can be in any order. However, in practical applications, it might be necessary to sort according to certain rules, such as the order in which vertices are added, or the order of vertex values, etc., to facilitate the quick search for vertices with certain extremal values. diff --git a/en/docs/chapter_heap/build_heap.md b/en/docs/chapter_heap/build_heap.md new file mode 100644 index 000000000..60edf2a5e --- /dev/null +++ b/en/docs/chapter_heap/build_heap.md @@ -0,0 +1,382 @@ +--- +comments: true +--- + +# 8.2   Heap construction operation + +In some cases, we want to build a heap using all elements of a list, and this process is known as "heap construction operation." + +## 8.2.1   Implementing with heap insertion operation + +First, we create an empty heap and then iterate through the list, performing the "heap insertion operation" on each element in turn. This means adding the element to the end of the heap and then "heapifying" it from bottom to top. + +Each time an element is added to the heap, the length of the heap increases by one. Since nodes are added to the binary tree from top to bottom, the heap is constructed "from top to bottom." + +Let the number of elements be $n$, and each element's insertion operation takes $O(\log{n})$ time, thus the time complexity of this heap construction method is $O(n \log n)$. + +## 8.2.2   Implementing by heapifying through traversal + +In fact, we can implement a more efficient method of heap construction in two steps. + +1. Add all elements of the list as they are into the heap, at this point the properties of the heap are not yet satisfied. +2. Traverse the heap in reverse order (reverse of level-order traversal), and perform "top to bottom heapify" on each non-leaf node. + +**After heapifying a node, the subtree with that node as the root becomes a valid sub-heap**. Since the traversal is in reverse order, the heap is built "from bottom to top." + +The reason for choosing reverse traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective. + +It's worth mentioning that **since leaf nodes have no children, they naturally form valid sub-heaps and do not need to be heapified**. As shown in the following code, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification: + +=== "Python" + + ```python title="my_heap.py" + def __init__(self, nums: list[int]): + """构造方法,根据输入列表建堆""" + # 将列表元素原封不动添加进堆 + self.max_heap = nums + # 堆化除叶节点以外的其他所有节点 + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) + ``` + +=== "C++" + + ```cpp title="my_heap.cpp" + /* 构造方法,根据输入列表建堆 */ + MaxHeap(vector nums) { + // 将列表元素原封不动添加进堆 + maxHeap = nums; + // 堆化除叶节点以外的其他所有节点 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + ``` + +=== "Java" + + ```java title="my_heap.java" + /* 构造方法,根据输入列表建堆 */ + MaxHeap(List nums) { + // 将列表元素原封不动添加进堆 + maxHeap = new ArrayList<>(nums); + // 堆化除叶节点以外的其他所有节点 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 构造函数,根据输入列表建堆 */ + MaxHeap(IEnumerable nums) { + // 将列表元素原封不动添加进堆 + maxHeap = new List(nums); + // 堆化除叶节点以外的其他所有节点 + var size = Parent(this.Size() - 1); + for (int i = size; i >= 0; i--) { + SiftDown(i); + } + } + ``` + +=== "Go" + + ```go title="my_heap.go" + /* 构造函数,根据切片建堆 */ + func newMaxHeap(nums []any) *maxHeap { + // 将列表元素原封不动添加进堆 + h := &maxHeap{data: nums} + for i := h.parent(len(h.data) - 1); i >= 0; i-- { + // 堆化除叶节点以外的其他所有节点 + h.siftDown(i) + } + return h + } + ``` + +=== "Swift" + + ```swift title="my_heap.swift" + /* 构造方法,根据输入列表建堆 */ + init(nums: [Int]) { + // 将列表元素原封不动添加进堆 + maxHeap = nums + // 堆化除叶节点以外的其他所有节点 + for i in (0 ... parent(i: size() - 1)).reversed() { + siftDown(i: i) + } + } + ``` + +=== "JS" + + ```javascript title="my_heap.js" + /* 构造方法,建立空堆或根据输入列表建堆 */ + constructor(nums) { + // 将列表元素原封不动添加进堆 + this.#maxHeap = nums === undefined ? [] : [...nums]; + // 堆化除叶节点以外的其他所有节点 + for (let i = this.#parent(this.size() - 1); i >= 0; i--) { + this.#siftDown(i); + } + } + ``` + +=== "TS" + + ```typescript title="my_heap.ts" + /* 构造方法,建立空堆或根据输入列表建堆 */ + constructor(nums?: number[]) { + // 将列表元素原封不动添加进堆 + this.maxHeap = nums === undefined ? [] : [...nums]; + // 堆化除叶节点以外的其他所有节点 + for (let i = this.parent(this.size() - 1); i >= 0; i--) { + this.siftDown(i); + } + } + ``` + +=== "Dart" + + ```dart title="my_heap.dart" + /* 构造方法,根据输入列表建堆 */ + MaxHeap(List nums) { + // 将列表元素原封不动添加进堆 + _maxHeap = nums; + // 堆化除叶节点以外的其他所有节点 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + ``` + +=== "Rust" + + ```rust title="my_heap.rs" + /* 构造方法,根据输入列表建堆 */ + fn new(nums: Vec) -> Self { + // 将列表元素原封不动添加进堆 + let mut heap = MaxHeap { max_heap: nums }; + // 堆化除叶节点以外的其他所有节点 + for i in (0..=Self::parent(heap.size() - 1)).rev() { + heap.sift_down(i); + } + heap + } + ``` + +=== "C" + + ```c title="my_heap.c" + /* 构造函数,根据切片建堆 */ + MaxHeap *newMaxHeap(int nums[], int size) { + // 所有元素入堆 + MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); + maxHeap->size = size; + memcpy(maxHeap->data, nums, size * sizeof(int)); + for (int i = parent(maxHeap, size - 1); i >= 0; i--) { + // 堆化除叶节点以外的其他所有节点 + siftDown(maxHeap, i); + } + return maxHeap; + } + ``` + +=== "Kotlin" + + ```kotlin title="my_heap.kt" + /* 大顶堆 */ + class MaxHeap(nums: List?) { + // 使用列表而非数组,这样无须考虑扩容问题 + // 将列表元素原封不动添加进堆 + private val maxHeap = ArrayList(nums!!) + + /* 构造函数,根据输入列表建堆 */ + init { + // 堆化除叶节点以外的其他所有节点 + for (i in parent(size() - 1) downTo 0) { + siftDown(i) + } + } + + /* 获取左子节点的索引 */ + private fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 获取右子节点的索引 */ + private fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 获取父节点的索引 */ + private fun parent(i: Int): Int { + return (i - 1) / 2 // 向下整除 + } + + /* 交换元素 */ + private fun swap(i: Int, j: Int) { + maxHeap[i] = maxHeap[j].also { maxHeap[j] = maxHeap[i] } + } + + /* 获取堆大小 */ + fun size(): Int { + return maxHeap.size + } + + /* 判断堆是否为空 */ + fun isEmpty(): Boolean { + /* 判断堆是否为空 */ + return size() == 0 + } + + /* 访问堆顶元素 */ + fun peek(): Int { + return maxHeap[0] + } + + /* 元素入堆 */ + fun push(value: Int) { + // 添加节点 + maxHeap.add(value) + // 从底至顶堆化 + siftUp(size() - 1) + } + + /* 从节点 i 开始,从底至顶堆化 */ + private fun siftUp(it: Int) { + // Kotlin的函数参数不可变,因此创建临时变量 + var i = it + while (true) { + // 获取节点 i 的父节点 + val p = parent(i) + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) break + // 交换两节点 + swap(i, p) + // 循环向上堆化 + i = p + } + } + + /* 元素出堆 */ + fun pop(): Int { + // 判空处理 + if (isEmpty()) throw IndexOutOfBoundsException() + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(0, size() - 1) + // 删除节点 + val value = maxHeap.removeAt(size() - 1) + // 从顶至底堆化 + siftDown(0) + // 返回堆顶元素 + return value + } + + /* 从节点 i 开始,从顶至底堆化 */ + private fun siftDown(it: Int) { + // Kotlin的函数参数不可变,因此创建临时变量 + var i = it + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + val l = left(i) + val r = right(i) + var ma = i + if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l + if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break + // 交换两节点 + swap(i, ma) + // 循环向下堆化 + i = ma + } + } + + /* 打印堆(二叉树) */ + fun print() { + val queue = PriorityQueue { a: Int, b: Int -> b - a } + queue.addAll(maxHeap) + printHeap(queue) + } + } + ``` + +=== "Ruby" + + ```ruby title="my_heap.rb" + [class]{MaxHeap}-[func]{__init__} + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 构造方法,根据输入列表建堆 + fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { + if (self.max_heap != null) return; + self.max_heap = std.ArrayList(T).init(allocator); + // 将列表元素原封不动添加进堆 + try self.max_heap.?.appendSlice(nums); + // 堆化除叶节点以外的其他所有节点 + var i: usize = parent(self.size() - 1) + 1; + while (i > 0) : (i -= 1) { + try self.siftDown(i - 1); + } + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +## 8.2.3   Complexity analysis + +Next, let's attempt to calculate the time complexity of this second method of heap construction. + +- Assuming the number of nodes in the complete binary tree is $n$, then the number of leaf nodes is $(n + 1) / 2$, where $/$ is integer division. Therefore, the number of nodes that need to be heapified is $(n - 1) / 2$. +- In the process of "top to bottom heapification," each node is heapified to the leaf nodes at most, so the maximum number of iterations is the height of the binary tree $\log n$. + +Multiplying the two, we get the time complexity of the heap construction process as $O(n \log n)$. **But this estimate is not accurate, because it does not take into account the nature of the binary tree having far more nodes at the lower levels than at the top.** + +Let's perform a more accurate calculation. To simplify the calculation, assume a "perfect binary tree" with $n$ nodes and height $h$; this assumption does not affect the correctness of the result. + +![Node counts at each level of a perfect binary tree](build_heap.assets/heapify_operations_count.png){ class="animation-figure" } + +

Figure 8-5   Node counts at each level of a perfect binary tree

+ +As shown in the Figure 8-5 , the maximum number of iterations for a node "to be heapified from top to bottom" is equal to the distance from that node to the leaf nodes, which is precisely "node height." Therefore, we can sum the "number of nodes $\times$ node height" at each level, **to get the total number of heapification iterations for all nodes**. + +$$ +T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 +$$ + +To simplify the above equation, we need to use knowledge of sequences from high school, first multiply $T(h)$ by $2$, to get: + +$$ +\begin{aligned} +T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline +2T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^h\times1 \newline +\end{aligned} +$$ + +By subtracting $T(h)$ from $2T(h)$ using the method of displacement, we get: + +$$ +2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h +$$ + +Observing the equation, $T(h)$ is an geometric series, which can be directly calculated using the sum formula, resulting in a time complexity of: + +$$ +\begin{aligned} +T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline +& = 2^{h+1} - h - 2 \newline +& = O(2^h) +\end{aligned} +$$ + +Further, a perfect binary tree with height $h$ has $n = 2^{h+1} - 1$ nodes, thus the complexity is $O(2^h) = O(n)$. This calculation shows that **the time complexity of inputting a list and constructing a heap is $O(n)$, which is very efficient**. diff --git a/en/docs/chapter_heap/heap.md b/en/docs/chapter_heap/heap.md new file mode 100644 index 000000000..658b3817d --- /dev/null +++ b/en/docs/chapter_heap/heap.md @@ -0,0 +1,1820 @@ +--- +comments: true +--- + +# 8.1   Heap + +A "heap" is a complete binary tree that satisfies specific conditions and can be mainly divided into two types, as shown in the Figure 8-1 . + +- "Min heap": The value of any node $\leq$ the values of its child nodes. +- "Max heap": The value of any node $\geq$ the values of its child nodes. + +![Min heap and max heap](heap.assets/min_heap_and_max_heap.png){ class="animation-figure" } + +

Figure 8-1   Min heap and max heap

+ +As a special case of a complete binary tree, heaps have the following characteristics: + +- The bottom layer nodes are filled from left to right, and nodes in other layers are fully filled. +- The root node of the binary tree is called the "heap top," and the bottom-rightmost node is called the "heap bottom." +- For max heaps (min heaps), the value of the heap top element (root node) is the largest (smallest). + +## 8.1.1   Common operations on heaps + +It should be noted that many programming languages provide a "priority queue," which is an abstract data structure defined as a queue with priority sorting. + +In fact, **heaps are often used to implement priority queues, with max heaps equivalent to priority queues where elements are dequeued in descending order**. From a usage perspective, we can consider "priority queue" and "heap" as equivalent data structures. Therefore, this book does not make a special distinction between the two, uniformly referring to them as "heap." + +Common operations on heaps are shown in the Table 8-1 , and the method names depend on the programming language. + +

Table 8-1   Efficiency of Heap Operations

+ +
+ +| Method name | Description | Time complexity | +| ----------- | ------------------------------------------------------------ | --------------- | +| `push()` | Add an element to the heap | $O(\log n)$ | +| `pop()` | Remove the top element from the heap | $O(\log n)$ | +| `peek()` | Access the top element (for max/min heap, the max/min value) | $O(1)$ | +| `size()` | Get the number of elements in the heap | $O(1)$ | +| `isEmpty()` | Check if the heap is empty | $O(1)$ | + +
+ +In practice, we can directly use the heap class (or priority queue class) provided by programming languages. + +Similar to sorting algorithms where we have "ascending order" and "descending order," we can switch between "min heap" and "max heap" by setting a `flag` or modifying the `Comparator`. The code is as follows: + +=== "Python" + + ```python title="heap.py" + # 初始化小顶堆 + min_heap, flag = [], 1 + # 初始化大顶堆 + max_heap, flag = [], -1 + + # Python 的 heapq 模块默认实现小顶堆 + # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + + # 元素入堆 + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + # 获取堆顶元素 + peek: int = flag * max_heap[0] # 5 + + # 堆顶元素出堆 + # 出堆元素会形成一个从大到小的序列 + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 + + # 获取堆大小 + size: int = len(max_heap) + + # 判断堆是否为空 + is_empty: bool = not max_heap + + # 输入列表并建堆 + min_heap: list[int] = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + ``` + +=== "C++" + + ```cpp title="heap.cpp" + /* 初始化堆 */ + // 初始化小顶堆 + priority_queue, greater> minHeap; + // 初始化大顶堆 + priority_queue, less> maxHeap; + + /* 元素入堆 */ + maxHeap.push(1); + maxHeap.push(3); + maxHeap.push(2); + maxHeap.push(5); + maxHeap.push(4); + + /* 获取堆顶元素 */ + int peek = maxHeap.top(); // 5 + + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + maxHeap.pop(); // 5 + maxHeap.pop(); // 4 + maxHeap.pop(); // 3 + maxHeap.pop(); // 2 + maxHeap.pop(); // 1 + + /* 获取堆大小 */ + int size = maxHeap.size(); + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.empty(); + + /* 输入列表并建堆 */ + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + ``` + +=== "Java" + + ```java title="heap.java" + /* 初始化堆 */ + // 初始化小顶堆 + Queue minHeap = new PriorityQueue<>(); + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + /* 元素入堆 */ + maxHeap.offer(1); + maxHeap.offer(3); + maxHeap.offer(2); + maxHeap.offer(5); + maxHeap.offer(4); + + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); // 5 + + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.poll(); // 5 + peek = maxHeap.poll(); // 4 + peek = maxHeap.poll(); // 3 + peek = maxHeap.poll(); // 2 + peek = maxHeap.poll(); // 1 + + /* 获取堆大小 */ + int size = maxHeap.size(); + + /* 判断堆是否为空 */ + boolean isEmpty = maxHeap.isEmpty(); + + /* 输入列表并建堆 */ + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + ``` + +=== "C#" + + ```csharp title="heap.cs" + /* 初始化堆 */ + // 初始化小顶堆 + PriorityQueue minHeap = new(); + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y - x)); + + /* 元素入堆 */ + maxHeap.Enqueue(1, 1); + maxHeap.Enqueue(3, 3); + maxHeap.Enqueue(2, 2); + maxHeap.Enqueue(5, 5); + maxHeap.Enqueue(4, 4); + + /* 获取堆顶元素 */ + int peek = maxHeap.Peek();//5 + + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.Dequeue(); // 5 + peek = maxHeap.Dequeue(); // 4 + peek = maxHeap.Dequeue(); // 3 + peek = maxHeap.Dequeue(); // 2 + peek = maxHeap.Dequeue(); // 1 + + /* 获取堆大小 */ + int size = maxHeap.Count; + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.Count == 0; + + /* 输入列表并建堆 */ + minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); + ``` + +=== "Go" + + ```go title="heap.go" + // Go 语言中可以通过实现 heap.Interface 来构建整数大顶堆 + // 实现 heap.Interface 需要同时实现 sort.Interface + type intHeap []any + + // Push heap.Interface 的方法,实现推入元素到堆 + func (h *intHeap) Push(x any) { + // Push 和 Pop 使用 pointer receiver 作为参数 + // 因为它们不仅会对切片的内容进行调整,还会修改切片的长度。 + *h = append(*h, x.(int)) + } + + // Pop heap.Interface 的方法,实现弹出堆顶元素 + func (h *intHeap) Pop() any { + // 待出堆元素存放在最后 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last + } + + // Len sort.Interface 的方法 + func (h *intHeap) Len() int { + return len(*h) + } + + // Less sort.Interface 的方法 + func (h *intHeap) Less(i, j int) bool { + // 如果实现小顶堆,则需要调整为小于号 + return (*h)[i].(int) > (*h)[j].(int) + } + + // Swap sort.Interface 的方法 + func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + } + + // Top 获取堆顶元素 + func (h *intHeap) Top() any { + return (*h)[0] + } + + /* Driver Code */ + func TestHeap(t *testing.T) { + /* 初始化堆 */ + // 初始化大顶堆 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 元素入堆 */ + // 调用 heap.Interface 的方法,来添加元素 + heap.Push(maxHeap, 1) + heap.Push(maxHeap, 3) + heap.Push(maxHeap, 2) + heap.Push(maxHeap, 4) + heap.Push(maxHeap, 5) + + /* 获取堆顶元素 */ + top := maxHeap.Top() + fmt.Printf("堆顶元素为 %d\n", top) + + /* 堆顶元素出堆 */ + // 调用 heap.Interface 的方法,来移除元素 + heap.Pop(maxHeap) // 5 + heap.Pop(maxHeap) // 4 + heap.Pop(maxHeap) // 3 + heap.Pop(maxHeap) // 2 + heap.Pop(maxHeap) // 1 + + /* 获取堆大小 */ + size := len(*maxHeap) + fmt.Printf("堆元素数量为 %d\n", size) + + /* 判断堆是否为空 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("堆是否为空 %t\n", isEmpty) + } + ``` + +=== "Swift" + + ```swift title="heap.swift" + /* 初始化堆 */ + // Swift 的 Heap 类型同时支持最大堆和最小堆,且需要引入 swift-collections + var heap = Heap() + + /* 元素入堆 */ + heap.insert(1) + heap.insert(3) + heap.insert(2) + heap.insert(5) + heap.insert(4) + + /* 获取堆顶元素 */ + var peek = heap.max()! + + /* 堆顶元素出堆 */ + peek = heap.removeMax() // 5 + peek = heap.removeMax() // 4 + peek = heap.removeMax() // 3 + peek = heap.removeMax() // 2 + peek = heap.removeMax() // 1 + + /* 获取堆大小 */ + let size = heap.count + + /* 判断堆是否为空 */ + let isEmpty = heap.isEmpty + + /* 输入列表并建堆 */ + let heap2 = Heap([1, 3, 2, 5, 4]) + ``` + +=== "JS" + + ```javascript title="heap.js" + // JavaScript 未提供内置 Heap 类 + ``` + +=== "TS" + + ```typescript title="heap.ts" + // TypeScript 未提供内置 Heap 类 + ``` + +=== "Dart" + + ```dart title="heap.dart" + // Dart 未提供内置 Heap 类 + ``` + +=== "Rust" + + ```rust title="heap.rs" + use std::collections::BinaryHeap; + use std::cmp::Reverse; + + /* 初始化堆 */ + // 初始化小顶堆 + let mut min_heap = BinaryHeap::>::new(); + // 初始化大顶堆 + let mut max_heap = BinaryHeap::new(); + + /* 元素入堆 */ + max_heap.push(1); + max_heap.push(3); + max_heap.push(2); + max_heap.push(5); + max_heap.push(4); + + /* 获取堆顶元素 */ + let peek = max_heap.peek().unwrap(); // 5 + + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + let peek = max_heap.pop().unwrap(); // 5 + let peek = max_heap.pop().unwrap(); // 4 + let peek = max_heap.pop().unwrap(); // 3 + let peek = max_heap.pop().unwrap(); // 2 + let peek = max_heap.pop().unwrap(); // 1 + + /* 获取堆大小 */ + let size = max_heap.len(); + + /* 判断堆是否为空 */ + let is_empty = max_heap.is_empty(); + + /* 输入列表并建堆 */ + let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); + ``` + +=== "C" + + ```c title="heap.c" + // C 未提供内置 Heap 类 + ``` + +=== "Kotlin" + + ```kotlin title="heap.kt" + /* 初始化堆 */ + // 初始化小顶堆 + var minHeap = PriorityQueue() + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + /* 元素入堆 */ + maxHeap.offer(1) + maxHeap.offer(3) + maxHeap.offer(2) + maxHeap.offer(5) + maxHeap.offer(4) + + /* 获取堆顶元素 */ + var peek = maxHeap.peek() // 5 + + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.poll() // 5 + peek = maxHeap.poll() // 4 + peek = maxHeap.poll() // 3 + peek = maxHeap.poll() // 2 + peek = maxHeap.poll() // 1 + + /* 获取堆大小 */ + val size = maxHeap.size + + /* 判断堆是否为空 */ + val isEmpty = maxHeap.isEmpty() + + /* 输入列表并建堆 */ + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + ``` + +=== "Ruby" + + ```ruby title="heap.rb" + + ``` + +=== "Zig" + + ```zig title="heap.zig" + + ``` + +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=import%20heapq%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%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 8.1.2   Implementation of heaps + +The following implementation is of a max heap. To convert it into a min heap, simply invert all size logic comparisons (for example, replace $\geq$ with $\leq$). Interested readers are encouraged to implement it on their own. + +### 1.   Storage and representation of heaps + +As mentioned in the "Binary Trees" section, complete binary trees are well-suited for array representation. Since heaps are a type of complete binary tree, **we will use arrays to store heaps**. + +When using an array to represent a binary tree, elements represent node values, and indexes represent node positions in the binary tree. **Node pointers are implemented through an index mapping formula**. + +As shown in the Figure 8-2 , given an index $i$, the index of its left child is $2i + 1$, the index of its right child is $2i + 2$, and the index of its parent is $(i - 1) / 2$ (floor division). When the index is out of bounds, it signifies a null node or the node does not exist. + +![Representation and storage of heaps](heap.assets/representation_of_heap.png){ class="animation-figure" } + +

Figure 8-2   Representation and storage of heaps

+ +We can encapsulate the index mapping formula into functions for convenient later use: + +=== "Python" + + ```python title="my_heap.py" + def left(self, i: int) -> int: + """获取左子节点的索引""" + return 2 * i + 1 + + def right(self, i: int) -> int: + """获取右子节点的索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int: + """获取父节点的索引""" + return (i - 1) // 2 # 向下整除 + ``` + +=== "C++" + + ```cpp title="my_heap.cpp" + /* 获取左子节点的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + ``` + +=== "Java" + + ```java title="my_heap.java" + /* 获取左子节点的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 获取左子节点的索引 */ + int Left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int Right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int Parent(int i) { + return (i - 1) / 2; // 向下整除 + } + ``` + +=== "Go" + + ```go title="my_heap.go" + /* 获取左子节点的索引 */ + func (h *maxHeap) left(i int) int { + return 2*i + 1 + } + + /* 获取右子节点的索引 */ + func (h *maxHeap) right(i int) int { + return 2*i + 2 + } + + /* 获取父节点的索引 */ + func (h *maxHeap) parent(i int) int { + // 向下整除 + return (i - 1) / 2 + } + ``` + +=== "Swift" + + ```swift title="my_heap.swift" + /* 获取左子节点的索引 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 获取右子节点的索引 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 获取父节点的索引 */ + func parent(i: Int) -> Int { + (i - 1) / 2 // 向下整除 + } + ``` + +=== "JS" + + ```javascript title="my_heap.js" + /* 获取左子节点的索引 */ + #left(i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + #right(i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + #parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + ``` + +=== "TS" + + ```typescript title="my_heap.ts" + /* 获取左子节点的索引 */ + left(i: number): number { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + right(i: number): number { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + ``` + +=== "Dart" + + ```dart title="my_heap.dart" + /* 获取左子节点的索引 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 向下整除 + } + ``` + +=== "Rust" + + ```rust title="my_heap.rs" + /* 获取左子节点的索引 */ + fn left(i: usize) -> usize { + 2 * i + 1 + } + + /* 获取右子节点的索引 */ + fn right(i: usize) -> usize { + 2 * i + 2 + } + + /* 获取父节点的索引 */ + fn parent(i: usize) -> usize { + (i - 1) / 2 // 向下整除 + } + ``` + +=== "C" + + ```c title="my_heap.c" + /* 获取左子节点的索引 */ + int left(MaxHeap *maxHeap, int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int right(MaxHeap *maxHeap, int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int parent(MaxHeap *maxHeap, int i) { + return (i - 1) / 2; + } + ``` + +=== "Kotlin" + + ```kotlin title="my_heap.kt" + /* 获取左子节点的索引 */ + fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 获取右子节点的索引 */ + fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 获取父节点的索引 */ + fun parent(i: Int): Int { + return (i - 1) / 2 // 向下整除 + } + ``` + +=== "Ruby" + + ```ruby title="my_heap.rb" + [class]{MaxHeap}-[func]{left} + + [class]{MaxHeap}-[func]{right} + + [class]{MaxHeap}-[func]{parent} + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 获取左子节点的索引 + fn left(i: usize) usize { + return 2 * i + 1; + } + + // 获取右子节点的索引 + fn right(i: usize) usize { + return 2 * i + 2; + } + + // 获取父节点的索引 + fn parent(i: usize) usize { + // return (i - 1) / 2; // 向下整除 + return @divFloor(i - 1, 2); + } + ``` + +### 2.   Accessing the top element of the heap + +The top element of the heap is the root node of the binary tree, which is also the first element of the list: + +=== "Python" + + ```python title="my_heap.py" + def peek(self) -> int: + """访问堆顶元素""" + return self.max_heap[0] + ``` + +=== "C++" + + ```cpp title="my_heap.cpp" + /* 访问堆顶元素 */ + int peek() { + return maxHeap[0]; + } + ``` + +=== "Java" + + ```java title="my_heap.java" + /* 访问堆顶元素 */ + int peek() { + return maxHeap.get(0); + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 访问堆顶元素 */ + int Peek() { + return maxHeap[0]; + } + ``` + +=== "Go" + + ```go title="my_heap.go" + /* 访问堆顶元素 */ + func (h *maxHeap) peek() any { + return h.data[0] + } + ``` + +=== "Swift" + + ```swift title="my_heap.swift" + /* 访问堆顶元素 */ + func peek() -> Int { + maxHeap[0] + } + ``` + +=== "JS" + + ```javascript title="my_heap.js" + /* 访问堆顶元素 */ + peek() { + return this.#maxHeap[0]; + } + ``` + +=== "TS" + + ```typescript title="my_heap.ts" + /* 访问堆顶元素 */ + peek(): number { + return this.maxHeap[0]; + } + ``` + +=== "Dart" + + ```dart title="my_heap.dart" + /* 访问堆顶元素 */ + int peek() { + return _maxHeap[0]; + } + ``` + +=== "Rust" + + ```rust title="my_heap.rs" + /* 访问堆顶元素 */ + fn peek(&self) -> Option { + self.max_heap.first().copied() + } + ``` + +=== "C" + + ```c title="my_heap.c" + /* 访问堆顶元素 */ + int peek(MaxHeap *maxHeap) { + return maxHeap->data[0]; + } + ``` + +=== "Kotlin" + + ```kotlin title="my_heap.kt" + /* 访问堆顶元素 */ + fun peek(): Int { + return maxHeap[0] + } + ``` + +=== "Ruby" + + ```ruby title="my_heap.rb" + [class]{MaxHeap}-[func]{peek} + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 访问堆顶元素 + fn peek(self: *Self) T { + return self.max_heap.?.items[0]; + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +### 3.   Inserting an element into the heap + +Given an element `val`, we first add it to the bottom of the heap. After addition, since `val` may be larger than other elements in the heap, the heap's integrity might be compromised, **thus it's necessary to repair the path from the inserted node to the root node**. This operation is called "heapifying". + +Considering starting from the node inserted, **perform heapify from bottom to top**. As shown in the Figure 8-3 , we compare the value of the inserted node with its parent node, and if the inserted node is larger, we swap them. Then continue this operation, repairing each node in the heap from bottom to top until passing the root node or encountering a node that does not need to be swapped. + +=== "<1>" + ![Steps of element insertion into the heap](heap.assets/heap_push_step1.png){ class="animation-figure" } + +=== "<2>" + ![heap_push_step2](heap.assets/heap_push_step2.png){ class="animation-figure" } + +=== "<3>" + ![heap_push_step3](heap.assets/heap_push_step3.png){ class="animation-figure" } + +=== "<4>" + ![heap_push_step4](heap.assets/heap_push_step4.png){ class="animation-figure" } + +=== "<5>" + ![heap_push_step5](heap.assets/heap_push_step5.png){ class="animation-figure" } + +=== "<6>" + ![heap_push_step6](heap.assets/heap_push_step6.png){ class="animation-figure" } + +=== "<7>" + ![heap_push_step7](heap.assets/heap_push_step7.png){ class="animation-figure" } + +=== "<8>" + ![heap_push_step8](heap.assets/heap_push_step8.png){ class="animation-figure" } + +=== "<9>" + ![heap_push_step9](heap.assets/heap_push_step9.png){ class="animation-figure" } + +

Figure 8-3   Steps of element insertion into the heap

+ +Given a total of $n$ nodes, the height of the tree is $O(\log n)$. Hence, the loop iterations for the heapify operation are at most $O(\log n)$, **making the time complexity of the element insertion operation $O(\log n)$**. The code is as shown: + +=== "Python" + + ```python title="my_heap.py" + def push(self, val: int): + """元素入堆""" + # 添加节点 + self.max_heap.append(val) + # 从底至顶堆化 + self.sift_up(self.size() - 1) + + def sift_up(self, i: int): + """从节点 i 开始,从底至顶堆化""" + while True: + # 获取节点 i 的父节点 + p = self.parent(i) + # 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # 交换两节点 + self.swap(i, p) + # 循环向上堆化 + i = p + ``` + +=== "C++" + + ```cpp title="my_heap.cpp" + /* 元素入堆 */ + void push(int val) { + // 添加节点 + maxHeap.push_back(val); + // 从底至顶堆化 + siftUp(size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交换两节点 + swap(maxHeap[i], maxHeap[p]); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "Java" + + ```java title="my_heap.java" + /* 元素入堆 */ + void push(int val) { + // 添加节点 + maxHeap.add(val); + // 从底至顶堆化 + siftUp(size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) + break; + // 交换两节点 + swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 元素入堆 */ + void Push(int val) { + // 添加节点 + maxHeap.Add(val); + // 从底至顶堆化 + SiftUp(Size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void SiftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = Parent(i); + // 若“越过根节点”或“节点无须修复”,则结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交换两节点 + Swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "Go" + + ```go title="my_heap.go" + /* 元素入堆 */ + func (h *maxHeap) push(val any) { + // 添加节点 + h.data = append(h.data, val) + // 从底至顶堆化 + h.siftUp(len(h.data) - 1) + } + + /* 从节点 i 开始,从底至顶堆化 */ + func (h *maxHeap) siftUp(i int) { + for true { + // 获取节点 i 的父节点 + p := h.parent(i) + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 || h.data[i].(int) <= h.data[p].(int) { + break + } + // 交换两节点 + h.swap(i, p) + // 循环向上堆化 + i = p + } + } + ``` + +=== "Swift" + + ```swift title="my_heap.swift" + /* 元素入堆 */ + func push(val: Int) { + // 添加节点 + maxHeap.append(val) + // 从底至顶堆化 + siftUp(i: size() - 1) + } + + /* 从节点 i 开始,从底至顶堆化 */ + func siftUp(i: Int) { + var i = i + while true { + // 获取节点 i 的父节点 + let p = parent(i: i) + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 || maxHeap[i] <= maxHeap[p] { + break + } + // 交换两节点 + swap(i: i, j: p) + // 循环向上堆化 + i = p + } + } + ``` + +=== "JS" + + ```javascript title="my_heap.js" + /* 元素入堆 */ + push(val) { + // 添加节点 + this.#maxHeap.push(val); + // 从底至顶堆化 + this.#siftUp(this.size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + #siftUp(i) { + while (true) { + // 获取节点 i 的父节点 + const p = this.#parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; + // 交换两节点 + this.#swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "TS" + + ```typescript title="my_heap.ts" + /* 元素入堆 */ + push(val: number): void { + // 添加节点 + this.maxHeap.push(val); + // 从底至顶堆化 + this.siftUp(this.size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + siftUp(i: number): void { + while (true) { + // 获取节点 i 的父节点 + const p = this.parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; + // 交换两节点 + this.swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "Dart" + + ```dart title="my_heap.dart" + /* 元素入堆 */ + void push(int val) { + // 添加节点 + _maxHeap.add(val); + // 从底至顶堆化 + siftUp(size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = _parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { + break; + } + // 交换两节点 + _swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "Rust" + + ```rust title="my_heap.rs" + /* 元素入堆 */ + fn push(&mut self, val: i32) { + // 添加节点 + self.max_heap.push(val); + // 从底至顶堆化 + self.sift_up(self.size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + fn sift_up(&mut self, mut i: usize) { + loop { + // 节点 i 已经是堆顶节点了,结束堆化 + if i == 0 { + break; + } + // 获取节点 i 的父节点 + let p = Self::parent(i); + // 当“节点无须修复”时,结束堆化 + if self.max_heap[i] <= self.max_heap[p] { + break; + } + // 交换两节点 + self.swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "C" + + ```c title="my_heap.c" + /* 元素入堆 */ + void push(MaxHeap *maxHeap, int val) { + // 默认情况下,不应该添加这么多节点 + if (maxHeap->size == MAX_SIZE) { + printf("heap is full!"); + return; + } + // 添加节点 + maxHeap->data[maxHeap->size] = val; + maxHeap->size++; + + // 从底至顶堆化 + siftUp(maxHeap, maxHeap->size - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(MaxHeap *maxHeap, int i) { + while (true) { + // 获取节点 i 的父节点 + int p = parent(maxHeap, i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { + break; + } + // 交换两节点 + swap(maxHeap, i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "Kotlin" + + ```kotlin title="my_heap.kt" + /* 元素入堆 */ + fun push(value: Int) { + // 添加节点 + maxHeap.add(value) + // 从底至顶堆化 + siftUp(size() - 1) + } + + /* 从节点 i 开始,从底至顶堆化 */ + fun siftUp(it: Int) { + // Kotlin的函数参数不可变,因此创建临时变量 + var i = it + while (true) { + // 获取节点 i 的父节点 + val p = parent(i) + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) break + // 交换两节点 + swap(i, p) + // 循环向上堆化 + i = p + } + } + ``` + +=== "Ruby" + + ```ruby title="my_heap.rb" + [class]{MaxHeap}-[func]{push} + + [class]{MaxHeap}-[func]{sift_up} + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 元素入堆 + fn push(self: *Self, val: T) !void { + // 添加节点 + try self.max_heap.?.append(val); + // 从底至顶堆化 + try self.siftUp(self.size() - 1); + } + + // 从节点 i 开始,从底至顶堆化 + fn siftUp(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 获取节点 i 的父节点 + var p = parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; + // 交换两节点 + try self.swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +### 4.   Removing the top element from the heap + +The top element of the heap is the root node of the binary tree, that is, the first element of the list. If we directly remove the first element from the list, all node indexes in the binary tree would change, making it difficult to use heapify for repairs subsequently. To minimize changes in element indexes, we use the following steps. + +1. Swap the top element with the bottom element of the heap (swap the root node with the rightmost leaf node). +2. After swapping, remove the bottom of the heap from the list (note, since it has been swapped, what is actually being removed is the original top element). +3. Starting from the root node, **perform heapify from top to bottom**. + +As shown in the Figure 8-4 , **the direction of "heapify from top to bottom" is opposite to "heapify from bottom to top"**. We compare the value of the root node with its two children and swap it with the largest child. Then repeat this operation until passing the leaf node or encountering a node that does not need to be swapped. + +=== "<1>" + ![Steps of removing the top element from the heap](heap.assets/heap_pop_step1.png){ class="animation-figure" } + +=== "<2>" + ![heap_pop_step2](heap.assets/heap_pop_step2.png){ class="animation-figure" } + +=== "<3>" + ![heap_pop_step3](heap.assets/heap_pop_step3.png){ class="animation-figure" } + +=== "<4>" + ![heap_pop_step4](heap.assets/heap_pop_step4.png){ class="animation-figure" } + +=== "<5>" + ![heap_pop_step5](heap.assets/heap_pop_step5.png){ class="animation-figure" } + +=== "<6>" + ![heap_pop_step6](heap.assets/heap_pop_step6.png){ class="animation-figure" } + +=== "<7>" + ![heap_pop_step7](heap.assets/heap_pop_step7.png){ class="animation-figure" } + +=== "<8>" + ![heap_pop_step8](heap.assets/heap_pop_step8.png){ class="animation-figure" } + +=== "<9>" + ![heap_pop_step9](heap.assets/heap_pop_step9.png){ class="animation-figure" } + +=== "<10>" + ![heap_pop_step10](heap.assets/heap_pop_step10.png){ class="animation-figure" } + +

Figure 8-4   Steps of removing the top element from the heap

+ +Similar to the element insertion operation, the time complexity of the top element removal operation is also $O(\log n)$. The code is as follows: + +=== "Python" + + ```python title="my_heap.py" + def pop(self) -> int: + """元素出堆""" + # 判空处理 + if self.is_empty(): + raise IndexError("堆为空") + # 交换根节点与最右叶节点(交换首元素与尾元素) + self.swap(0, self.size() - 1) + # 删除节点 + val = self.max_heap.pop() + # 从顶至底堆化 + self.sift_down(0) + # 返回堆顶元素 + return val + + def sift_down(self, i: int): + """从节点 i 开始,从顶至底堆化""" + while True: + # 判断节点 i, l, r 中值最大的节点,记为 ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i: + break + # 交换两节点 + self.swap(i, ma) + # 循环向下堆化 + i = ma + ``` + +=== "C++" + + ```cpp title="my_heap.cpp" + /* 元素出堆 */ + void pop() { + // 判空处理 + if (isEmpty()) { + throw out_of_range("堆为空"); + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(maxHeap[0], maxHeap[size() - 1]); + // 删除节点 + maxHeap.pop_back(); + // 从顶至底堆化 + siftDown(0); + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) + break; + swap(maxHeap[i], maxHeap[ma]); + // 循环向下堆化 + i = ma; + } + } + ``` + +=== "Java" + + ```java title="my_heap.java" + /* 元素出堆 */ + int pop() { + // 判空处理 + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(0, size() - 1); + // 删除节点 + int val = maxHeap.remove(size() - 1); + // 从顶至底堆化 + siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) + ma = l; + if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) + break; + // 交换两节点 + swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 元素出堆 */ + int Pop() { + // 判空处理 + if (IsEmpty()) + throw new IndexOutOfRangeException(); + // 交换根节点与最右叶节点(交换首元素与尾元素) + Swap(0, Size() - 1); + // 删除节点 + int val = maxHeap.Last(); + maxHeap.RemoveAt(Size() - 1); + // 从顶至底堆化 + SiftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void SiftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = Left(i), r = Right(i), ma = i; + if (l < Size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < Size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若“节点 i 最大”或“越过叶节点”,则结束堆化 + if (ma == i) break; + // 交换两节点 + Swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + +=== "Go" + + ```go title="my_heap.go" + /* 元素出堆 */ + func (h *maxHeap) pop() any { + // 判空处理 + if h.isEmpty() { + fmt.Println("error") + return nil + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + h.swap(0, h.size()-1) + // 删除节点 + val := h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + // 从顶至底堆化 + h.siftDown(0) + + // 返回堆顶元素 + return val + } + + /* 从节点 i 开始,从顶至底堆化 */ + func (h *maxHeap) siftDown(i int) { + for true { + // 判断节点 i, l, r 中值最大的节点,记为 max + l, r, max := h.left(i), h.right(i), i + if l < h.size() && h.data[l].(int) > h.data[max].(int) { + max = l + } + if r < h.size() && h.data[r].(int) > h.data[max].(int) { + max = r + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if max == i { + break + } + // 交换两节点 + h.swap(i, max) + // 循环向下堆化 + i = max + } + } + ``` + +=== "Swift" + + ```swift title="my_heap.swift" + /* 元素出堆 */ + func pop() -> Int { + // 判空处理 + if isEmpty() { + fatalError("堆为空") + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(i: 0, j: size() - 1) + // 删除节点 + let val = maxHeap.remove(at: size() - 1) + // 从顶至底堆化 + siftDown(i: 0) + // 返回堆顶元素 + return val + } + + /* 从节点 i 开始,从顶至底堆化 */ + func siftDown(i: Int) { + var i = i + while true { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let l = left(i: i) + let r = right(i: i) + var ma = i + if l < size(), maxHeap[l] > maxHeap[ma] { + ma = l + } + if r < size(), maxHeap[r] > maxHeap[ma] { + ma = r + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i { + break + } + // 交换两节点 + swap(i: i, j: ma) + // 循环向下堆化 + i = ma + } + } + ``` + +=== "JS" + + ```javascript title="my_heap.js" + /* 元素出堆 */ + pop() { + // 判空处理 + if (this.isEmpty()) throw new Error('堆为空'); + // 交换根节点与最右叶节点(交换首元素与尾元素) + this.#swap(0, this.size() - 1); + // 删除节点 + const val = this.#maxHeap.pop(); + // 从顶至底堆化 + this.#siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + #siftDown(i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + const l = this.#left(i), + r = this.#right(i); + let ma = i; + if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; + if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma === i) break; + // 交换两节点 + this.#swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + +=== "TS" + + ```typescript title="my_heap.ts" + /* 元素出堆 */ + pop(): number { + // 判空处理 + if (this.isEmpty()) throw new RangeError('Heap is empty.'); + // 交换根节点与最右叶节点(交换首元素与尾元素) + this.swap(0, this.size() - 1); + // 删除节点 + const val = this.maxHeap.pop(); + // 从顶至底堆化 + this.siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + siftDown(i: number): void { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + const l = this.left(i), + r = this.right(i); + let ma = i; + if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; + if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma === i) break; + // 交换两节点 + this.swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + +=== "Dart" + + ```dart title="my_heap.dart" + /* 元素出堆 */ + int pop() { + // 判空处理 + if (isEmpty()) throw Exception('堆为空'); + // 交换根节点与最右叶节点(交换首元素与尾元素) + _swap(0, size() - 1); + // 删除节点 + int val = _maxHeap.removeLast(); + // 从顶至底堆化 + siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = _left(i); + int r = _right(i); + int ma = i; + if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; + if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break; + // 交换两节点 + _swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + +=== "Rust" + + ```rust title="my_heap.rs" + /* 元素出堆 */ + fn pop(&mut self) -> i32 { + // 判空处理 + if self.is_empty() { + panic!("index out of bounds"); + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + self.swap(0, self.size() - 1); + // 删除节点 + let val = self.max_heap.remove(self.size() - 1); + // 从顶至底堆化 + self.sift_down(0); + // 返回堆顶元素 + val + } + + /* 从节点 i 开始,从顶至底堆化 */ + fn sift_down(&mut self, mut i: usize) { + loop { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let (l, r, mut ma) = (Self::left(i), Self::right(i), i); + if l < self.size() && self.max_heap[l] > self.max_heap[ma] { + ma = l; + } + if r < self.size() && self.max_heap[r] > self.max_heap[ma] { + ma = r; + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i { + break; + } + // 交换两节点 + self.swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + +=== "C" + + ```c title="my_heap.c" + /* 元素出堆 */ + int pop(MaxHeap *maxHeap) { + // 判空处理 + if (isEmpty(maxHeap)) { + printf("heap is empty!"); + return INT_MAX; + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(maxHeap, 0, size(maxHeap) - 1); + // 删除节点 + int val = maxHeap->data[maxHeap->size - 1]; + maxHeap->size--; + // 从顶至底堆化 + siftDown(maxHeap, 0); + + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(MaxHeap *maxHeap, int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 max + int l = left(maxHeap, i); + int r = right(maxHeap, i); + int max = i; + if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { + max = l; + } + if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { + max = r; + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (max == i) { + break; + } + // 交换两节点 + swap(maxHeap, i, max); + // 循环向下堆化 + i = max; + } + } + ``` + +=== "Kotlin" + + ```kotlin title="my_heap.kt" + /* 元素出堆 */ + fun pop(): Int { + // 判空处理 + if (isEmpty()) throw IndexOutOfBoundsException() + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(0, size() - 1) + // 删除节点 + val value = maxHeap.removeAt(size() - 1) + // 从顶至底堆化 + siftDown(0) + // 返回堆顶元素 + return value + } + + /* 从节点 i 开始,从顶至底堆化 */ + fun siftDown(it: Int) { + // Kotlin的函数参数不可变,因此创建临时变量 + var i = it + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + val l = left(i) + val r = right(i) + var ma = i + if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l + if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break + // 交换两节点 + swap(i, ma) + // 循环向下堆化 + i = ma + } + } + ``` + +=== "Ruby" + + ```ruby title="my_heap.rb" + [class]{MaxHeap}-[func]{pop} + + [class]{MaxHeap}-[func]{sift_down} + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 元素出堆 + fn pop(self: *Self) !T { + // 判断处理 + if (self.isEmpty()) unreachable; + // 交换根节点与最右叶节点(交换首元素与尾元素) + try self.swap(0, self.size() - 1); + // 删除节点 + var val = self.max_heap.?.pop(); + // 从顶至底堆化 + try self.siftDown(0); + // 返回堆顶元素 + return val; + } + + // 从节点 i 开始,从顶至底堆化 + fn siftDown(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + var l = left(i); + var r = right(i); + var ma = i; + if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; + if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break; + // 交换两节点 + try self.swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +## 8.1.3   Common applications of heaps + +- **Priority Queue**: Heaps are often the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of $O(\log n)$, and building a queue having a time complexity of $O(n)$, all of which are very efficient. +- **Heap Sort**: Given a set of data, we can create a heap from them and then continually perform element removal operations to obtain ordered data. However, we usually use a more elegant method to implement heap sort, as detailed in the "Heap Sort" section. +- **Finding the Largest $k$ Elements**: This is a classic algorithm problem and also a typical application, such as selecting the top 10 hot news for Weibo hot search, picking the top 10 selling products, etc. diff --git a/en/docs/chapter_heap/index.md b/en/docs/chapter_heap/index.md new file mode 100644 index 000000000..55c022d03 --- /dev/null +++ b/en/docs/chapter_heap/index.md @@ -0,0 +1,21 @@ +--- +comments: true +icon: material/family-tree +--- + +# Chapter 8.   Heap + +![Heap](../assets/covers/chapter_heap.jpg){ class="cover-image" } + +!!! abstract + + The heap is like mountain peaks, stacked and undulating, each with its unique shape. + + Among these peaks, the highest one always catches the eye first. + +## Chapter Contents + +- [8.1   Heap](https://www.hello-algo.com/en/chapter_heap/heap/) +- [8.2   Building a Heap](https://www.hello-algo.com/en/chapter_heap/build_heap/) +- [8.3   Top-k Problem](https://www.hello-algo.com/en/chapter_heap/top_k/) +- [8.4   Summary](https://www.hello-algo.com/en/chapter_heap/summary/) diff --git a/en/docs/chapter_heap/summary.md b/en/docs/chapter_heap/summary.md new file mode 100644 index 000000000..9d4e4c991 --- /dev/null +++ b/en/docs/chapter_heap/summary.md @@ -0,0 +1,21 @@ +--- +comments: true +--- + +# 8.4   Summary + +### 1.   Key review + +- A heap is a complete binary tree, which can be divided into a max heap and a min heap based on its property. The top element of a max (min) heap is the largest (smallest). +- A priority queue is defined as a queue with dequeue priority, usually implemented using a heap. +- Common operations of a heap and their corresponding time complexities include: element insertion into the heap $O(\log n)$, removing the top element from the heap $O(\log n)$, and accessing the top element of the heap $O(1)$. +- A complete binary tree is well-suited to be represented by an array, thus heaps are commonly stored using arrays. +- Heapify operations are used to maintain the properties of the heap and are used in both heap insertion and removal operations. +- The time complexity of inserting $n$ elements into a heap and building the heap can be optimized to $O(n)$, which is highly efficient. +- Top-k is a classic algorithm problem that can be efficiently solved using the heap data structure, with a time complexity of $O(n \log k)$. + +### 2.   Q & A + +**Q**: Is the "heap" in data structures the same concept as the "heap" in memory management? + +The two are not the same concept, even though they are both referred to as "heap". The heap in computer system memory is part of dynamic memory allocation, where the program can use it to store data during execution. The program can request a certain amount of heap memory to store complex structures like objects and arrays. When these data are no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, the management and usage of heap memory need to be more cautious, as improper use may lead to memory leaks and dangling pointers. diff --git a/en/docs/chapter_heap/top_k.md b/en/docs/chapter_heap/top_k.md new file mode 100644 index 000000000..ec5ca84e7 --- /dev/null +++ b/en/docs/chapter_heap/top_k.md @@ -0,0 +1,456 @@ +--- +comments: true +--- + +# 8.3   Top-k problem + +!!! question + + Given an unordered array `nums` of length $n$, return the largest $k$ elements in the array. + +For this problem, we will first introduce two straightforward solutions, then explain a more efficient heap-based method. + +## 8.3.1   Method 1: Iterative selection + +We can perform $k$ rounds of iterations as shown in the Figure 8-6 , extracting the $1^{st}$, $2^{nd}$, $\dots$, $k^{th}$ largest elements in each round, with a time complexity of $O(nk)$. + +This method is only suitable when $k \ll n$, as the time complexity approaches $O(n^2)$ when $k$ is close to $n$, which is very time-consuming. + +![Iteratively finding the largest k elements](top_k.assets/top_k_traversal.png){ class="animation-figure" } + +

Figure 8-6   Iteratively finding the largest k elements

+ +!!! tip + + When $k = n$, we can obtain a complete ordered sequence, which is equivalent to the "selection sort" algorithm. + +## 8.3.2   Method 2: Sorting + +As shown in the Figure 8-7 , we can first sort the array `nums` and then return the last $k$ elements, with a time complexity of $O(n \log n)$. + +Clearly, this method "overachieves" the task, as we only need to find the largest $k$ elements, without the need to sort the other elements. + +![Sorting to find the largest k elements](top_k.assets/top_k_sorting.png){ class="animation-figure" } + +

Figure 8-7   Sorting to find the largest k elements

+ +## 8.3.3   Method 3: Heap + +We can solve the Top-k problem more efficiently based on heaps, as shown in the following process. + +1. Initialize a min heap, where the top element is the smallest. +2. First, insert the first $k$ elements of the array into the heap. +3. Starting from the $k + 1^{th}$ element, if the current element is greater than the top element of the heap, remove the top element of the heap and insert the current element into the heap. +4. After completing the traversal, the heap contains the largest $k$ elements. + +=== "<1>" + ![Find the largest k elements based on heap](top_k.assets/top_k_heap_step1.png){ class="animation-figure" } + +=== "<2>" + ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png){ class="animation-figure" } + +=== "<3>" + ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png){ class="animation-figure" } + +=== "<4>" + ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png){ class="animation-figure" } + +=== "<5>" + ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png){ class="animation-figure" } + +=== "<6>" + ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png){ class="animation-figure" } + +=== "<7>" + ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png){ class="animation-figure" } + +=== "<8>" + ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png){ class="animation-figure" } + +=== "<9>" + ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png){ class="animation-figure" } + +

Figure 8-8   Find the largest k elements based on heap

+ +Example code is as follows: + +=== "Python" + + ```python title="top_k.py" + def top_k_heap(nums: list[int], k: int) -> list[int]: + """基于堆查找数组中最大的 k 个元素""" + # 初始化小顶堆 + heap = [] + # 将数组的前 k 个元素入堆 + for i in range(k): + heapq.heappush(heap, nums[i]) + # 从第 k+1 个元素开始,保持堆的长度为 k + for i in range(k, len(nums)): + # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > heap[0]: + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + return heap + ``` + +=== "C++" + + ```cpp title="top_k.cpp" + /* 基于堆查找数组中最大的 k 个元素 */ + priority_queue, greater> topKHeap(vector &nums, int k) { + // 初始化小顶堆 + priority_queue, greater> heap; + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.size(); i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; + } + ``` + +=== "Java" + + ```java title="top_k.java" + /* 基于堆查找数组中最大的 k 个元素 */ + Queue topKHeap(int[] nums, int k) { + // 初始化小顶堆 + Queue heap = new PriorityQueue(); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + ``` + +=== "C#" + + ```csharp title="top_k.cs" + /* 基于堆查找数组中最大的 k 个元素 */ + PriorityQueue TopKHeap(int[] nums, int k) { + // 初始化小顶堆 + PriorityQueue heap = new(); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.Enqueue(nums[i], nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.Length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.Peek()) { + heap.Dequeue(); + heap.Enqueue(nums[i], nums[i]); + } + } + return heap; + } + ``` + +=== "Go" + + ```go title="top_k.go" + /* 基于堆查找数组中最大的 k 个元素 */ + func topKHeap(nums []int, k int) *minHeap { + // 初始化小顶堆 + h := &minHeap{} + heap.Init(h) + // 将数组的前 k 个元素入堆 + for i := 0; i < k; i++ { + heap.Push(h, nums[i]) + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for i := k; i < len(nums); i++ { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > h.Top().(int) { + heap.Pop(h) + heap.Push(h, nums[i]) + } + } + return h + } + ``` + +=== "Swift" + + ```swift title="top_k.swift" + /* 基于堆查找数组中最大的 k 个元素 */ + func topKHeap(nums: [Int], k: Int) -> [Int] { + // 初始化一个小顶堆,并将前 k 个元素建堆 + var heap = Heap(nums.prefix(k)) + // 从第 k+1 个元素开始,保持堆的长度为 k + for i in nums.indices.dropFirst(k) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > heap.min()! { + _ = heap.removeMin() + heap.insert(nums[i]) + } + } + return heap.unordered + } + ``` + +=== "JS" + + ```javascript title="top_k.js" + /* 元素入堆 */ + function pushMinHeap(maxHeap, val) { + // 元素取反 + maxHeap.push(-val); + } + + /* 元素出堆 */ + function popMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.pop(); + } + + /* 访问堆顶元素 */ + function peekMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.peek(); + } + + /* 取出堆中元素 */ + function getMinHeap(maxHeap) { + // 元素取反 + return maxHeap.getMaxHeap().map((num) => -num); + } + + /* 基于堆查找数组中最大的 k 个元素 */ + function topKHeap(nums, k) { + // 初始化小顶堆 + // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 + const maxHeap = new MaxHeap([]); + // 将数组的前 k 个元素入堆 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (let i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆中元素 + return getMinHeap(maxHeap); + } + ``` + +=== "TS" + + ```typescript title="top_k.ts" + /* 元素入堆 */ + function pushMinHeap(maxHeap: MaxHeap, val: number): void { + // 元素取反 + maxHeap.push(-val); + } + + /* 元素出堆 */ + function popMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.pop(); + } + + /* 访问堆顶元素 */ + function peekMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.peek(); + } + + /* 取出堆中元素 */ + function getMinHeap(maxHeap: MaxHeap): number[] { + // 元素取反 + return maxHeap.getMaxHeap().map((num: number) => -num); + } + + /* 基于堆查找数组中最大的 k 个元素 */ + function topKHeap(nums: number[], k: number): number[] { + // 初始化小顶堆 + // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 + const maxHeap = new MaxHeap([]); + // 将数组的前 k 个元素入堆 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (let i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆中元素 + return getMinHeap(maxHeap); + } + ``` + +=== "Dart" + + ```dart title="top_k.dart" + /* 基于堆查找数组中最大的 k 个元素 */ + MinHeap topKHeap(List nums, int k) { + // 初始化小顶堆,将数组的前 k 个元素入堆 + MinHeap heap = MinHeap(nums.sublist(0, k)); + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.peek()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; + } + ``` + +=== "Rust" + + ```rust title="top_k.rs" + /* 基于堆查找数组中最大的 k 个元素 */ + fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { + // BinaryHeap 是大顶堆,使用 Reverse 将元素取反,从而实现小顶堆 + let mut heap = BinaryHeap::>::new(); + // 将数组的前 k 个元素入堆 + for &num in nums.iter().take(k) { + heap.push(Reverse(num)); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for &num in nums.iter().skip(k) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if num > heap.peek().unwrap().0 { + heap.pop(); + heap.push(Reverse(num)); + } + } + heap + } + ``` + +=== "C" + + ```c title="top_k.c" + /* 元素入堆 */ + void pushMinHeap(MaxHeap *maxHeap, int val) { + // 元素取反 + push(maxHeap, -val); + } + + /* 元素出堆 */ + int popMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -pop(maxHeap); + } + + /* 访问堆顶元素 */ + int peekMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -peek(maxHeap); + } + + /* 取出堆中元素 */ + int *getMinHeap(MaxHeap *maxHeap) { + // 将堆中所有元素取反并存入 res 数组 + int *res = (int *)malloc(maxHeap->size * sizeof(int)); + for (int i = 0; i < maxHeap->size; i++) { + res[i] = -maxHeap->data[i]; + } + return res; + } + + /* 取出堆中元素 */ + int *getMinHeap(MaxHeap *maxHeap) { + // 将堆中所有元素取反并存入 res 数组 + int *res = (int *)malloc(maxHeap->size * sizeof(int)); + for (int i = 0; i < maxHeap->size; i++) { + res[i] = -maxHeap->data[i]; + } + return res; + } + + // 基于堆查找数组中最大的 k 个元素的函数 + int *topKHeap(int *nums, int sizeNums, int k) { + // 初始化小顶堆 + // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 + int *empty = (int *)malloc(0); + MaxHeap *maxHeap = newMaxHeap(empty, 0); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < sizeNums; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + int *res = getMinHeap(maxHeap); + // 释放内存 + delMaxHeap(maxHeap); + return res; + } + ``` + +=== "Kotlin" + + ```kotlin title="top_k.kt" + /* 基于堆查找数组中最大的 k 个元素 */ + fun topKHeap(nums: IntArray, k: Int): Queue { + // 初始化小顶堆 + val heap = PriorityQueue() + // 将数组的前 k 个元素入堆 + for (i in 0.. heap.peek()) { + heap.poll() + heap.offer(nums[i]) + } + } + return heap + } + ``` + +=== "Ruby" + + ```ruby title="top_k.rb" + [class]{}-[func]{top_k_heap} + ``` + +=== "Zig" + + ```zig title="top_k.zig" + [class]{}-[func]{topKHeap} + ``` + +??? pythontutor "Code Visualization" + +
+ + +A total of $n$ rounds of heap insertions and deletions are performed, with the maximum heap size being $k$, hence the time complexity is $O(n \log k)$. This method is very efficient; when $k$ is small, the time complexity tends towards $O(n)$; when $k$ is large, the time complexity will not exceed $O(n \log n)$. + +Additionally, this method is suitable for scenarios with dynamic data streams. By continuously adding data, we can maintain the elements within the heap, thereby achieving dynamic updates of the largest $k$ elements. diff --git a/en/docs/chapter_tree/array_representation_of_tree.md b/en/docs/chapter_tree/array_representation_of_tree.md new file mode 100644 index 000000000..b34dfbe9f --- /dev/null +++ b/en/docs/chapter_tree/array_representation_of_tree.md @@ -0,0 +1,1280 @@ +--- +comments: true +--- + +# 7.3   Array representation of binary trees + +Under the linked list representation, the storage unit of a binary tree is a node `TreeNode`, with nodes connected by pointers. The basic operations of binary trees under the linked list representation were introduced in the previous section. + +So, can we use an array to represent a binary tree? The answer is yes. + +## 7.3.1   Representing perfect binary trees + +Let's analyze a simple case first. Given a perfect binary tree, we store all nodes in an array according to the order of level-order traversal, where each node corresponds to a unique array index. + +Based on the characteristics of level-order traversal, we can deduce a "mapping formula" between the index of a parent node and its children: **If a node's index is $i$, then the index of its left child is $2i + 1$ and the right child is $2i + 2$**. The Figure 7-12 shows the mapping relationship between the indices of various nodes. + +![Array representation of a perfect binary tree](array_representation_of_tree.assets/array_representation_binary_tree.png){ class="animation-figure" } + +

Figure 7-12   Array representation of a perfect binary tree

+ +**The mapping formula plays a role similar to the node references (pointers) in linked lists**. Given any node in the array, we can access its left (right) child node using the mapping formula. + +## 7.3.2   Representing any binary tree + +Perfect binary trees are a special case; there are often many `None` values in the middle levels of a binary tree. Since the sequence of level-order traversal does not include these `None` values, we cannot solely rely on this sequence to deduce the number and distribution of `None` values. **This means that multiple binary tree structures can match the same level-order traversal sequence**. + +As shown in the Figure 7-13 , given a non-perfect binary tree, the above method of array representation fails. + +![Level-order traversal sequence corresponds to multiple binary tree possibilities](array_representation_of_tree.assets/array_representation_without_empty.png){ class="animation-figure" } + +

Figure 7-13   Level-order traversal sequence corresponds to multiple binary tree possibilities

+ +To solve this problem, **we can consider explicitly writing out all `None` values in the level-order traversal sequence**. As shown in the following figure, after this treatment, the level-order traversal sequence can uniquely represent a binary tree. Example code is as follows: + +=== "Python" + + ```python title="" + # Array representation of a binary tree + # Using None to represent empty slots + tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + ``` + +=== "C++" + + ```cpp title="" + /* Array representation of a binary tree */ + // Using the maximum integer value INT_MAX to mark empty slots + vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Java" + + ```java title="" + /* Array representation of a binary tree */ + // Using the Integer wrapper class allows for using null to mark empty slots + Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ``` + +=== "C#" + + ```csharp title="" + /* Array representation of a binary tree */ + // Using nullable int (int?) allows for using null to mark empty slots + int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Go" + + ```go title="" + /* Array representation of a binary tree */ + // Using an any type slice, allowing for nil to mark empty slots + tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + ``` + +=== "Swift" + + ```swift title="" + /* Array representation of a binary tree */ + // Using optional Int (Int?) allows for using nil to mark empty slots + let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "JS" + + ```javascript title="" + /* Array representation of a binary tree */ + // Using null to represent empty slots + let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "TS" + + ```typescript title="" + /* Array representation of a binary tree */ + // Using null to represent empty slots + let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Dart" + + ```dart title="" + /* Array representation of a binary tree */ + // Using nullable int (int?) allows for using null to mark empty slots + List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Rust" + + ```rust title="" + /* Array representation of a binary tree */ + // Using None to mark empty slots + let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; + ``` + +=== "C" + + ```c title="" + /* Array representation of a binary tree */ + // Using the maximum int value to mark empty slots, therefore, node values must not be INT_MAX + int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Array representation of a binary tree */ + // Using null to represent empty slots + val tree = mutableListOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +![Array representation of any type of binary tree](array_representation_of_tree.assets/array_representation_with_empty.png){ class="animation-figure" } + +

Figure 7-14   Array representation of any type of binary tree

+ +It's worth noting that **complete binary trees are very suitable for array representation**. Recalling the definition of a complete binary tree, `None` appears only at the bottom level and towards the right, **meaning all `None` values definitely appear at the end of the level-order traversal sequence**. + +This means that when using an array to represent a complete binary tree, it's possible to omit storing all `None` values, which is very convenient. The Figure 7-15 gives an example. + +![Array representation of a complete binary tree](array_representation_of_tree.assets/array_representation_complete_binary_tree.png){ class="animation-figure" } + +

Figure 7-15   Array representation of a complete binary tree

+ +The following code implements a binary tree based on array representation, including the following operations: + +- Given a node, obtain its value, left (right) child node, and parent node. +- Obtain the preorder, inorder, postorder, and level-order traversal sequences. + +=== "Python" + + ```python title="array_binary_tree.py" + class ArrayBinaryTree: + """数组表示下的二叉树类""" + + def __init__(self, arr: list[int | None]): + """构造方法""" + self._tree = list(arr) + + def size(self): + """列表容量""" + return len(self._tree) + + def val(self, i: int) -> int: + """获取索引为 i 节点的值""" + # 若索引越界,则返回 None ,代表空位 + if i < 0 or i >= self.size(): + return None + return self._tree[i] + + def left(self, i: int) -> int | None: + """获取索引为 i 节点的左子节点的索引""" + return 2 * i + 1 + + def right(self, i: int) -> int | None: + """获取索引为 i 节点的右子节点的索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int | None: + """获取索引为 i 节点的父节点的索引""" + return (i - 1) // 2 + + def level_order(self) -> list[int]: + """层序遍历""" + self.res = [] + # 直接遍历数组 + for i in range(self.size()): + if self.val(i) is not None: + self.res.append(self.val(i)) + return self.res + + def dfs(self, i: int, order: str): + """深度优先遍历""" + if self.val(i) is None: + return + # 前序遍历 + if order == "pre": + self.res.append(self.val(i)) + self.dfs(self.left(i), order) + # 中序遍历 + if order == "in": + self.res.append(self.val(i)) + self.dfs(self.right(i), order) + # 后序遍历 + if order == "post": + self.res.append(self.val(i)) + + def pre_order(self) -> list[int]: + """前序遍历""" + self.res = [] + self.dfs(0, order="pre") + return self.res + + def in_order(self) -> list[int]: + """中序遍历""" + self.res = [] + self.dfs(0, order="in") + return self.res + + def post_order(self) -> list[int]: + """后序遍历""" + self.res = [] + self.dfs(0, order="post") + return self.res + ``` + +=== "C++" + + ```cpp title="array_binary_tree.cpp" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + public: + /* 构造方法 */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* 列表容量 */ + int size() { + return tree.size(); + } + + /* 获取索引为 i 节点的值 */ + int val(int i) { + // 若索引越界,则返回 INT_MAX ,代表空位 + if (i < 0 || i >= size()) + return INT_MAX; + return tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + int parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + vector levelOrder() { + vector res; + // 直接遍历数组 + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* 前序遍历 */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* 中序遍历 */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* 后序遍历 */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + private: + vector tree; + + /* 深度优先遍历 */ + void dfs(int i, string order, vector &res) { + // 若为空位,则返回 + if (val(i) == INT_MAX) + return; + // 前序遍历 + if (order == "pre") + res.push_back(val(i)); + dfs(left(i), order, res); + // 中序遍历 + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // 后序遍历 + if (order == "post") + res.push_back(val(i)); + } + }; + ``` + +=== "Java" + + ```java title="array_binary_tree.java" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + private List tree; + + /* 构造方法 */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } + + /* 列表容量 */ + public int size() { + return tree.size(); + } + + /* 获取索引为 i 节点的值 */ + public Integer val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } + + /* 获取索引为 i 节点的左子节点的索引 */ + public Integer left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + public Integer right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + public Integer parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + public List levelOrder() { + List res = new ArrayList<>(); + // 直接遍历数组 + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + return res; + } + + /* 深度优先遍历 */ + private void dfs(Integer i, String order, List res) { + // 若为空位,则返回 + if (val(i) == null) + return; + // 前序遍历 + if ("pre".equals(order)) + res.add(val(i)); + dfs(left(i), order, res); + // 中序遍历 + if ("in".equals(order)) + res.add(val(i)); + dfs(right(i), order, res); + // 后序遍历 + if ("post".equals(order)) + res.add(val(i)); + } + + /* 前序遍历 */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } + + /* 中序遍历 */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } + + /* 后序遍历 */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } + } + ``` + +=== "C#" + + ```csharp title="array_binary_tree.cs" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree(List arr) { + List tree = new(arr); + + /* 列表容量 */ + public int Size() { + return tree.Count; + } + + /* 获取索引为 i 节点的值 */ + public int? Val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= Size()) + return null; + return tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + public int Left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + public int Right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + public int Parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + public List LevelOrder() { + List res = []; + // 直接遍历数组 + for (int i = 0; i < Size(); i++) { + if (Val(i).HasValue) + res.Add(Val(i)!.Value); + } + return res; + } + + /* 深度优先遍历 */ + void DFS(int i, string order, List res) { + // 若为空位,则返回 + if (!Val(i).HasValue) + return; + // 前序遍历 + if (order == "pre") + res.Add(Val(i)!.Value); + DFS(Left(i), order, res); + // 中序遍历 + if (order == "in") + res.Add(Val(i)!.Value); + DFS(Right(i), order, res); + // 后序遍历 + if (order == "post") + res.Add(Val(i)!.Value); + } + + /* 前序遍历 */ + public List PreOrder() { + List res = []; + DFS(0, "pre", res); + return res; + } + + /* 中序遍历 */ + public List InOrder() { + List res = []; + DFS(0, "in", res); + return res; + } + + /* 后序遍历 */ + public List PostOrder() { + List res = []; + DFS(0, "post", res); + return res; + } + } + ``` + +=== "Go" + + ```go title="array_binary_tree.go" + /* 数组表示下的二叉树类 */ + type arrayBinaryTree struct { + tree []any + } + + /* 构造方法 */ + func newArrayBinaryTree(arr []any) *arrayBinaryTree { + return &arrayBinaryTree{ + tree: arr, + } + } + + /* 列表容量 */ + func (abt *arrayBinaryTree) size() int { + return len(abt.tree) + } + + /* 获取索引为 i 节点的值 */ + func (abt *arrayBinaryTree) val(i int) any { + // 若索引越界,则返回 null ,代表空位 + if i < 0 || i >= abt.size() { + return nil + } + return abt.tree[i] + } + + /* 获取索引为 i 节点的左子节点的索引 */ + func (abt *arrayBinaryTree) left(i int) int { + return 2*i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + func (abt *arrayBinaryTree) right(i int) int { + return 2*i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + func (abt *arrayBinaryTree) parent(i int) int { + return (i - 1) / 2 + } + + /* 层序遍历 */ + func (abt *arrayBinaryTree) levelOrder() []any { + var res []any + // 直接遍历数组 + for i := 0; i < abt.size(); i++ { + if abt.val(i) != nil { + res = append(res, abt.val(i)) + } + } + return res + } + + /* 深度优先遍历 */ + func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { + // 若为空位,则返回 + if abt.val(i) == nil { + return + } + // 前序遍历 + if order == "pre" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.left(i), order, res) + // 中序遍历 + if order == "in" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.right(i), order, res) + // 后序遍历 + if order == "post" { + *res = append(*res, abt.val(i)) + } + } + + /* 前序遍历 */ + func (abt *arrayBinaryTree) preOrder() []any { + var res []any + abt.dfs(0, "pre", &res) + return res + } + + /* 中序遍历 */ + func (abt *arrayBinaryTree) inOrder() []any { + var res []any + abt.dfs(0, "in", &res) + return res + } + + /* 后序遍历 */ + func (abt *arrayBinaryTree) postOrder() []any { + var res []any + abt.dfs(0, "post", &res) + return res + } + ``` + +=== "Swift" + + ```swift title="array_binary_tree.swift" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + private var tree: [Int?] + + /* 构造方法 */ + init(arr: [Int?]) { + tree = arr + } + + /* 列表容量 */ + func size() -> Int { + tree.count + } + + /* 获取索引为 i 节点的值 */ + func val(i: Int) -> Int? { + // 若索引越界,则返回 null ,代表空位 + if i < 0 || i >= size() { + return nil + } + return tree[i] + } + + /* 获取索引为 i 节点的左子节点的索引 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + func parent(i: Int) -> Int { + (i - 1) / 2 + } + + /* 层序遍历 */ + func levelOrder() -> [Int] { + var res: [Int] = [] + // 直接遍历数组 + for i in 0 ..< size() { + if let val = val(i: i) { + res.append(val) + } + } + return res + } + + /* 深度优先遍历 */ + private func dfs(i: Int, order: String, res: inout [Int]) { + // 若为空位,则返回 + guard let val = val(i: i) else { + return + } + // 前序遍历 + if order == "pre" { + res.append(val) + } + dfs(i: left(i: i), order: order, res: &res) + // 中序遍历 + if order == "in" { + res.append(val) + } + dfs(i: right(i: i), order: order, res: &res) + // 后序遍历 + if order == "post" { + res.append(val) + } + } + + /* 前序遍历 */ + func preOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "pre", res: &res) + return res + } + + /* 中序遍历 */ + func inOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "in", res: &res) + return res + } + + /* 后序遍历 */ + func postOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "post", res: &res) + return res + } + } + ``` + +=== "JS" + + ```javascript title="array_binary_tree.js" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + #tree; + + /* 构造方法 */ + constructor(arr) { + this.#tree = arr; + } + + /* 列表容量 */ + size() { + return this.#tree.length; + } + + /* 获取索引为 i 节点的值 */ + val(i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + left(i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + right(i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 层序遍历 */ + levelOrder() { + let res = []; + // 直接遍历数组 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度优先遍历 */ + #dfs(i, order, res) { + // 若为空位,则返回 + if (this.val(i) === null) return; + // 前序遍历 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序遍历 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 后序遍历 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序遍历 */ + preOrder() { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序遍历 */ + inOrder() { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 后序遍历 */ + postOrder() { + const res = []; + this.#dfs(0, 'post', res); + return res; + } + } + ``` + +=== "TS" + + ```typescript title="array_binary_tree.ts" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + #tree: (number | null)[]; + + /* 构造方法 */ + constructor(arr: (number | null)[]) { + this.#tree = arr; + } + + /* 列表容量 */ + size(): number { + return this.#tree.length; + } + + /* 获取索引为 i 节点的值 */ + val(i: number): number | null { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + left(i: number): number { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + right(i: number): number { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 层序遍历 */ + levelOrder(): number[] { + let res = []; + // 直接遍历数组 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度优先遍历 */ + #dfs(i: number, order: Order, res: (number | null)[]): void { + // 若为空位,则返回 + if (this.val(i) === null) return; + // 前序遍历 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序遍历 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 后序遍历 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序遍历 */ + preOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序遍历 */ + inOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 后序遍历 */ + postOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'post', res); + return res; + } + } + ``` + +=== "Dart" + + ```dart title="array_binary_tree.dart" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + late List _tree; + + /* 构造方法 */ + ArrayBinaryTree(this._tree); + + /* 列表容量 */ + int size() { + return _tree.length; + } + + /* 获取索引为 i 节点的值 */ + int? val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) { + return null; + } + return _tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + int? left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + int? right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + int? parent(int i) { + return (i - 1) ~/ 2; + } + + /* 层序遍历 */ + List levelOrder() { + List res = []; + for (int i = 0; i < size(); i++) { + if (val(i) != null) { + res.add(val(i)!); + } + } + return res; + } + + /* 深度优先遍历 */ + void dfs(int i, String order, List res) { + // 若为空位,则返回 + if (val(i) == null) { + return; + } + // 前序遍历 + if (order == 'pre') { + res.add(val(i)); + } + dfs(left(i)!, order, res); + // 中序遍历 + if (order == 'in') { + res.add(val(i)); + } + dfs(right(i)!, order, res); + // 后序遍历 + if (order == 'post') { + res.add(val(i)); + } + } + + /* 前序遍历 */ + List preOrder() { + List res = []; + dfs(0, 'pre', res); + return res; + } + + /* 中序遍历 */ + List inOrder() { + List res = []; + dfs(0, 'in', res); + return res; + } + + /* 后序遍历 */ + List postOrder() { + List res = []; + dfs(0, 'post', res); + return res; + } + } + ``` + +=== "Rust" + + ```rust title="array_binary_tree.rs" + /* 数组表示下的二叉树类 */ + struct ArrayBinaryTree { + tree: Vec>, + } + + impl ArrayBinaryTree { + /* 构造方法 */ + fn new(arr: Vec>) -> Self { + Self { tree: arr } + } + + /* 列表容量 */ + fn size(&self) -> i32 { + self.tree.len() as i32 + } + + /* 获取索引为 i 节点的值 */ + fn val(&self, i: i32) -> Option { + // 若索引越界,则返回 None ,代表空位 + if i < 0 || i >= self.size() { + None + } else { + self.tree[i as usize] + } + } + + /* 获取索引为 i 节点的左子节点的索引 */ + fn left(&self, i: i32) -> i32 { + 2 * i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + fn right(&self, i: i32) -> i32 { + 2 * i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + fn parent(&self, i: i32) -> i32 { + (i - 1) / 2 + } + + /* 层序遍历 */ + fn level_order(&self) -> Vec { + let mut res = vec![]; + // 直接遍历数组 + for i in 0..self.size() { + if let Some(val) = self.val(i) { + res.push(val) + } + } + res + } + + /* 深度优先遍历 */ + fn dfs(&self, i: i32, order: &str, res: &mut Vec) { + if self.val(i).is_none() { + return; + } + let val = self.val(i).unwrap(); + // 前序遍历 + if order == "pre" { + res.push(val); + } + self.dfs(self.left(i), order, res); + // 中序遍历 + if order == "in" { + res.push(val); + } + self.dfs(self.right(i), order, res); + // 后序遍历 + if order == "post" { + res.push(val); + } + } + + /* 前序遍历 */ + fn pre_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "pre", &mut res); + res + } + + /* 中序遍历 */ + fn in_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "in", &mut res); + res + } + + /* 后序遍历 */ + fn post_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "post", &mut res); + res + } + } + ``` + +=== "C" + + ```c title="array_binary_tree.c" + /* 数组表示下的二叉树结构体 */ + typedef struct { + int *tree; + int size; + } ArrayBinaryTree; + + /* 构造函数 */ + ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { + ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); + abt->tree = malloc(sizeof(int) * arrSize); + memcpy(abt->tree, arr, sizeof(int) * arrSize); + abt->size = arrSize; + return abt; + } + + /* 析构函数 */ + void delArrayBinaryTree(ArrayBinaryTree *abt) { + free(abt->tree); + free(abt); + } + + /* 列表容量 */ + int size(ArrayBinaryTree *abt) { + return abt->size; + } + + /* 获取索引为 i 节点的值 */ + int val(ArrayBinaryTree *abt, int i) { + // 若索引越界,则返回 INT_MAX ,代表空位 + if (i < 0 || i >= size(abt)) + return INT_MAX; + return abt->tree[i]; + } + + /* 层序遍历 */ + int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + // 直接遍历数组 + for (int i = 0; i < size(abt); i++) { + if (val(abt, i) != INT_MAX) + res[index++] = val(abt, i); + } + *returnSize = index; + return res; + } + + /* 深度优先遍历 */ + void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { + // 若为空位,则返回 + if (val(abt, i) == INT_MAX) + return; + // 前序遍历 + if (strcmp(order, "pre") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, left(i), order, res, index); + // 中序遍历 + if (strcmp(order, "in") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, right(i), order, res, index); + // 后序遍历 + if (strcmp(order, "post") == 0) + res[(*index)++] = val(abt, i); + } + + /* 前序遍历 */ + int *preOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "pre", res, &index); + *returnSize = index; + return res; + } + + /* 中序遍历 */ + int *inOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "in", res, &index); + *returnSize = index; + return res; + } + + /* 后序遍历 */ + int *postOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "post", res, &index); + *returnSize = index; + return res; + } + ``` + +=== "Kotlin" + + ```kotlin title="array_binary_tree.kt" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree(val tree: List) { + /* 列表容量 */ + fun size(): Int { + return tree.size + } + + /* 获取索引为 i 节点的值 */ + fun value(i: Int): Int? { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) return null + return tree[i] + } + + /* 获取索引为 i 节点的左子节点的索引 */ + fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + fun parent(i: Int): Int { + return (i - 1) / 2 + } + + /* 层序遍历 */ + fun levelOrder(): List { + val res = ArrayList() + // 直接遍历数组 + for (i in 0..) { + // 若为空位,则返回 + if (value(i) == null) return + // 前序遍历 + if ("pre" == order) res.add(value(i)) + dfs(left(i), order, res) + // 中序遍历 + if ("in" == order) res.add(value(i)) + dfs(right(i), order, res) + // 后序遍历 + if ("post" == order) res.add(value(i)) + } + + /* 前序遍历 */ + fun preOrder(): List { + val res = ArrayList() + dfs(0, "pre", res) + return res + } + + /* 中序遍历 */ + fun inOrder(): List { + val res = ArrayList() + dfs(0, "in", res) + return res + } + + /* 后序遍历 */ + fun postOrder(): List { + val res = ArrayList() + dfs(0, "post", res) + return res + } + } + ``` + +=== "Ruby" + + ```ruby title="array_binary_tree.rb" + [class]{ArrayBinaryTree}-[func]{} + ``` + +=== "Zig" + + ```zig title="array_binary_tree.zig" + [class]{ArrayBinaryTree}-[func]{} + ``` + +??? pythontutor "Code Visualization" + +
+ + +## 7.3.3   Advantages and limitations + +The array representation of binary trees has the following advantages: + +- Arrays are stored in contiguous memory spaces, which is cache-friendly and allows for faster access and traversal. +- It does not require storing pointers, which saves space. +- It allows random access to nodes. + +However, the array representation also has some limitations: + +- Array storage requires contiguous memory space, so it is not suitable for storing trees with a large amount of data. +- Adding or deleting nodes requires array insertion and deletion operations, which are less efficient. +- When there are many `None` values in the binary tree, the proportion of node data contained in the array is low, leading to lower space utilization. diff --git a/en/docs/chapter_tree/avl_tree.md b/en/docs/chapter_tree/avl_tree.md new file mode 100644 index 000000000..f5dc32480 --- /dev/null +++ b/en/docs/chapter_tree/avl_tree.md @@ -0,0 +1,2691 @@ +--- +comments: true +--- + +# 7.5   AVL tree * + +In the "Binary Search Tree" section, we mentioned that after multiple insertions and removals, a binary search tree might degrade to a linked list. In such cases, the time complexity of all operations degrades from $O(\log n)$ to $O(n)$. + +As shown in the Figure 7-24 , after two node removal operations, this binary search tree will degrade into a linked list. + +![Degradation of an AVL tree after removing nodes](avl_tree.assets/avltree_degradation_from_removing_node.png){ class="animation-figure" } + +

Figure 7-24   Degradation of an AVL tree after removing nodes

+ +For example, in the perfect binary tree shown in the Figure 7-25 , after inserting two nodes, the tree will lean heavily to the left, and the time complexity of search operations will also degrade. + +![Degradation of an AVL tree after inserting nodes](avl_tree.assets/avltree_degradation_from_inserting_node.png){ class="animation-figure" } + +

Figure 7-25   Degradation of an AVL tree after inserting nodes

+ +In 1962, G. M. Adelson-Velsky and E. M. Landis proposed the "AVL Tree" in their paper "An algorithm for the organization of information". The paper detailed a series of operations to ensure that after continuously adding and removing nodes, the AVL tree would not degrade, thus maintaining the time complexity of various operations at $O(\log n)$ level. In other words, in scenarios where frequent additions, removals, searches, and modifications are needed, the AVL tree can always maintain efficient data operation performance, which has great application value. + +## 7.5.1   Common terminology in AVL trees + +An AVL tree is both a binary search tree and a balanced binary tree, satisfying all properties of these two types of binary trees, hence it is a "balanced binary search tree". + +### 1.   Node height + +Since the operations related to AVL trees require obtaining node heights, we need to add a `height` variable to the node class: + +=== "Python" + + ```python title="" + class TreeNode: + """AVL tree node""" + def __init__(self, val: int): + self.val: int = val # Node value + self.height: int = 0 # Node height + self.left: TreeNode | None = None # Left child reference + self.right: TreeNode | None = None # Right child reference + ``` + +=== "C++" + + ```cpp title="" + /* AVL tree node */ + struct TreeNode { + int val{}; // Node value + int height = 0; // Node height + TreeNode *left{}; // Left child + TreeNode *right{}; // Right child + TreeNode() = default; + explicit TreeNode(int x) : val(x){} + }; + ``` + +=== "Java" + + ```java title="" + /* AVL tree node */ + class TreeNode { + public int val; // Node value + public int height; // Node height + public TreeNode left; // Left child + public TreeNode right; // Right child + public TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* AVL tree node */ + class TreeNode(int? x) { + public int? val = x; // Node value + public int height; // Node height + public TreeNode? left; // Left child reference + public TreeNode? right; // Right child reference + } + ``` + +=== "Go" + + ```go title="" + /* AVL tree node */ + type TreeNode struct { + Val int // Node value + Height int // Node height + Left *TreeNode // Left child reference + Right *TreeNode // Right child reference + } + ``` + +=== "Swift" + + ```swift title="" + /* AVL tree node */ + class TreeNode { + var val: Int // Node value + var height: Int // Node height + var left: TreeNode? // Left child + var right: TreeNode? // Right child + + init(x: Int) { + val = x + height = 0 + } + } + ``` + +=== "JS" + + ```javascript title="" + /* AVL tree node */ + class TreeNode { + val; // Node value + height; // Node height + left; // Left child pointer + right; // Right child pointer + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* AVL tree node */ + class TreeNode { + val: number; // Node value + height: number; // Node height + left: TreeNode | null; // Left child pointer + right: TreeNode | null; // Right child pointer + constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "Dart" + + ```dart title="" + /* AVL tree node */ + class TreeNode { + int val; // Node value + int height; // Node height + TreeNode? left; // Left child + TreeNode? right; // Right child + TreeNode(this.val, [this.height = 0, this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* AVL tree node */ + struct TreeNode { + val: i32, // Node value + height: i32, // Node height + left: Option>>, // Left child + right: Option>>, // Right child + } + + impl TreeNode { + /* Constructor */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* AVL tree node */ + TreeNode struct TreeNode { + int val; + int height; + struct TreeNode *left; + struct TreeNode *right; + } TreeNode; + + /* Constructor */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* AVL tree node */ + class TreeNode(val _val: Int) { // Node value + val height: Int = 0 // Node height + val left: TreeNode? = null // Left child + val right: TreeNode? = null // Right child + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +The "node height" refers to the distance from that node to its farthest leaf node, i.e., the number of "edges" passed. It is important to note that the height of a leaf node is $0$, and the height of a null node is $-1$. We will create two utility functions for getting and updating the height of a node: + +=== "Python" + + ```python title="avl_tree.py" + def height(self, node: TreeNode | None) -> int: + """获取节点高度""" + # 空节点高度为 -1 ,叶节点高度为 0 + if node is not None: + return node.height + return -1 + + def update_height(self, node: TreeNode | None): + """更新节点高度""" + # 节点高度等于最高子树高度 + 1 + node.height = max([self.height(node.left), self.height(node.right)]) + 1 + ``` + +=== "C++" + + ```cpp title="avl_tree.cpp" + /* 获取节点高度 */ + int height(TreeNode *node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == nullptr ? -1 : node->height; + } + + /* 更新节点高度 */ + void updateHeight(TreeNode *node) { + // 节点高度等于最高子树高度 + 1 + node->height = max(height(node->left), height(node->right)) + 1; + } + ``` + +=== "Java" + + ```java title="avl_tree.java" + /* 获取节点高度 */ + int height(TreeNode node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == null ? -1 : node.height; + } + + /* 更新节点高度 */ + void updateHeight(TreeNode node) { + // 节点高度等于最高子树高度 + 1 + node.height = Math.max(height(node.left), height(node.right)) + 1; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 获取节点高度 */ + int Height(TreeNode? node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == null ? -1 : node.height; + } + + /* 更新节点高度 */ + void UpdateHeight(TreeNode node) { + // 节点高度等于最高子树高度 + 1 + node.height = Math.Max(Height(node.left), Height(node.right)) + 1; + } + ``` + +=== "Go" + + ```go title="avl_tree.go" + /* 获取节点高度 */ + func (t *aVLTree) height(node *TreeNode) int { + // 空节点高度为 -1 ,叶节点高度为 0 + if node != nil { + return node.Height + } + return -1 + } + + /* 更新节点高度 */ + func (t *aVLTree) updateHeight(node *TreeNode) { + lh := t.height(node.Left) + rh := t.height(node.Right) + // 节点高度等于最高子树高度 + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } + } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" + /* 获取节点高度 */ + func height(node: TreeNode?) -> Int { + // 空节点高度为 -1 ,叶节点高度为 0 + node?.height ?? -1 + } + + /* 更新节点高度 */ + func updateHeight(node: TreeNode?) { + // 节点高度等于最高子树高度 + 1 + node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 + } + ``` + +=== "JS" + + ```javascript title="avl_tree.js" + /* 获取节点高度 */ + height(node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node === null ? -1 : node.height; + } + + /* 更新节点高度 */ + #updateHeight(node) { + // 节点高度等于最高子树高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + ``` + +=== "TS" + + ```typescript title="avl_tree.ts" + /* 获取节点高度 */ + height(node: TreeNode): number { + // 空节点高度为 -1 ,叶节点高度为 0 + return node === null ? -1 : node.height; + } + + /* 更新节点高度 */ + updateHeight(node: TreeNode): void { + // 节点高度等于最高子树高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + ``` + +=== "Dart" + + ```dart title="avl_tree.dart" + /* 获取节点高度 */ + int height(TreeNode? node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == null ? -1 : node.height; + } + + /* 更新节点高度 */ + void updateHeight(TreeNode? node) { + // 节点高度等于最高子树高度 + 1 + node!.height = max(height(node.left), height(node.right)) + 1; + } + ``` + +=== "Rust" + + ```rust title="avl_tree.rs" + /* 获取节点高度 */ + fn height(node: OptionTreeNodeRc) -> i32 { + // 空节点高度为 -1 ,叶节点高度为 0 + match node { + Some(node) => node.borrow().height, + None => -1, + } + } + + /* 更新节点高度 */ + fn update_height(node: OptionTreeNodeRc) { + if let Some(node) = node { + let left = node.borrow().left.clone(); + let right = node.borrow().right.clone(); + // 节点高度等于最高子树高度 + 1 + node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; + } + } + ``` + +=== "C" + + ```c title="avl_tree.c" + /* 获取节点高度 */ + int height(TreeNode *node) { + // 空节点高度为 -1 ,叶节点高度为 0 + if (node != NULL) { + return node->height; + } + return -1; + } + + /* 更新节点高度 */ + void updateHeight(TreeNode *node) { + int lh = height(node->left); + int rh = height(node->right); + // 节点高度等于最高子树高度 + 1 + if (lh > rh) { + node->height = lh + 1; + } else { + node->height = rh + 1; + } + } + ``` + +=== "Kotlin" + + ```kotlin title="avl_tree.kt" + /* 获取节点高度 */ + fun height(node: TreeNode?): Int { + // 空节点高度为 -1 ,叶节点高度为 0 + return node?.height ?: -1 + } + + /* 更新节点高度 */ + fun updateHeight(node: TreeNode?) { + // 节点高度等于最高子树高度 + 1 + node?.height = (max(height(node?.left).toDouble(), height(node?.right).toDouble()) + 1).toInt() + } + ``` + +=== "Ruby" + + ```ruby title="avl_tree.rb" + [class]{AVLTree}-[func]{height} + + [class]{AVLTree}-[func]{update_height} + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 获取节点高度 + fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { + _ = self; + // 空节点高度为 -1 ,叶节点高度为 0 + return if (node == null) -1 else node.?.height; + } + + // 更新节点高度 + fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { + // 节点高度等于最高子树高度 + 1 + node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; + } + ``` + +### 2.   Node balance factor + +The "balance factor" of a node is defined as the height of the node's left subtree minus the height of its right subtree, with the balance factor of a null node defined as $0$. We will also encapsulate the functionality of obtaining the node balance factor into a function for easy use later on: + +=== "Python" + + ```python title="avl_tree.py" + def balance_factor(self, node: TreeNode | None) -> int: + """获取平衡因子""" + # 空节点平衡因子为 0 + if node is None: + return 0 + # 节点平衡因子 = 左子树高度 - 右子树高度 + return self.height(node.left) - self.height(node.right) + ``` + +=== "C++" + + ```cpp title="avl_tree.cpp" + /* 获取平衡因子 */ + int balanceFactor(TreeNode *node) { + // 空节点平衡因子为 0 + if (node == nullptr) + return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node->left) - height(node->right); + } + ``` + +=== "Java" + + ```java title="avl_tree.java" + /* 获取平衡因子 */ + int balanceFactor(TreeNode node) { + // 空节点平衡因子为 0 + if (node == null) + return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right); + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 获取平衡因子 */ + int BalanceFactor(TreeNode? node) { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return Height(node.left) - Height(node.right); + } + ``` + +=== "Go" + + ```go title="avl_tree.go" + /* 获取平衡因子 */ + func (t *aVLTree) balanceFactor(node *TreeNode) int { + // 空节点平衡因子为 0 + if node == nil { + return 0 + } + // 节点平衡因子 = 左子树高度 - 右子树高度 + return t.height(node.Left) - t.height(node.Right) + } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" + /* 获取平衡因子 */ + func balanceFactor(node: TreeNode?) -> Int { + // 空节点平衡因子为 0 + guard let node = node else { return 0 } + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node: node.left) - height(node: node.right) + } + ``` + +=== "JS" + + ```javascript title="avl_tree.js" + /* 获取平衡因子 */ + balanceFactor(node) { + // 空节点平衡因子为 0 + if (node === null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return this.height(node.left) - this.height(node.right); + } + ``` + +=== "TS" + + ```typescript title="avl_tree.ts" + /* 获取平衡因子 */ + balanceFactor(node: TreeNode): number { + // 空节点平衡因子为 0 + if (node === null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return this.height(node.left) - this.height(node.right); + } + ``` + +=== "Dart" + + ```dart title="avl_tree.dart" + /* 获取平衡因子 */ + int balanceFactor(TreeNode? node) { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right); + } + ``` + +=== "Rust" + + ```rust title="avl_tree.rs" + /* 获取平衡因子 */ + fn balance_factor(node: OptionTreeNodeRc) -> i32 { + match node { + // 空节点平衡因子为 0 + None => 0, + // 节点平衡因子 = 左子树高度 - 右子树高度 + Some(node) => { + Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) + } + } + } + ``` + +=== "C" + + ```c title="avl_tree.c" + /* 获取平衡因子 */ + int balanceFactor(TreeNode *node) { + // 空节点平衡因子为 0 + if (node == NULL) { + return 0; + } + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node->left) - height(node->right); + } + ``` + +=== "Kotlin" + + ```kotlin title="avl_tree.kt" + /* 获取平衡因子 */ + fun balanceFactor(node: TreeNode?): Int { + // 空节点平衡因子为 0 + if (node == null) return 0 + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right) + } + ``` + +=== "Ruby" + + ```ruby title="avl_tree.rb" + [class]{AVLTree}-[func]{balance_factor} + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 获取平衡因子 + fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return self.height(node.?.left) - self.height(node.?.right); + } + ``` + +!!! note + + Let the balance factor be $f$, then the balance factor of any node in an AVL tree satisfies $-1 \le f \le 1$. + +## 7.5.2   Rotations in AVL trees + +The characteristic feature of an AVL tree is the "rotation" operation, which can restore balance to an unbalanced node without affecting the in-order traversal sequence of the binary tree. In other words, **the rotation operation can maintain the property of a "binary search tree" while also turning the tree back into a "balanced binary tree"**. + +We call nodes with an absolute balance factor $> 1$ "unbalanced nodes". Depending on the type of imbalance, there are four kinds of rotations: right rotation, left rotation, right-left rotation, and left-right rotation. Below, we detail these rotation operations. + +### 1.   Right rotation + +As shown in the Figure 7-26 , the first unbalanced node from the bottom up in the binary tree is "node 3". Focusing on the subtree with this unbalanced node as the root, denoted as `node`, and its left child as `child`, perform a "right rotation". After the right rotation, the subtree is balanced again while still maintaining the properties of a binary search tree. + +=== "<1>" + ![Steps of right rotation](avl_tree.assets/avltree_right_rotate_step1.png){ class="animation-figure" } + +=== "<2>" + ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png){ class="animation-figure" } + +=== "<3>" + ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png){ class="animation-figure" } + +=== "<4>" + ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png){ class="animation-figure" } + +

Figure 7-26   Steps of right rotation

+ +As shown in the Figure 7-27 , when the `child` node has a right child (denoted as `grand_child`), a step needs to be added in the right rotation: set `grand_child` as the left child of `node`. + +![Right rotation with grand_child](avl_tree.assets/avltree_right_rotate_with_grandchild.png){ class="animation-figure" } + +

Figure 7-27   Right rotation with grand_child

+ +"Right rotation" is a figurative term; in practice, it is achieved by modifying node pointers, as shown in the following code: + +=== "Python" + + ```python title="avl_tree.py" + def right_rotate(self, node: TreeNode | None) -> TreeNode | None: + """右旋操作""" + child = node.left + grand_child = child.right + # 以 child 为原点,将 node 向右旋转 + child.right = node + node.left = grand_child + # 更新节点高度 + self.update_height(node) + self.update_height(child) + # 返回旋转后子树的根节点 + return child + ``` + +=== "C++" + + ```cpp title="avl_tree.cpp" + /* 右旋操作 */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child = node->left; + TreeNode *grandChild = child->right; + // 以 child 为原点,将 node 向右旋转 + child->right = node; + node->left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Java" + + ```java title="avl_tree.java" + /* 右旋操作 */ + TreeNode rightRotate(TreeNode node) { + TreeNode child = node.left; + TreeNode grandChild = child.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 右旋操作 */ + TreeNode? RightRotate(TreeNode? node) { + TreeNode? child = node?.left; + TreeNode? grandChild = child?.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Go" + + ```go title="avl_tree.go" + /* 右旋操作 */ + func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // 以 child 为原点,将 node 向右旋转 + child.Right = node + node.Left = grandChild + // 更新节点高度 + t.updateHeight(node) + t.updateHeight(child) + // 返回旋转后子树的根节点 + return child + } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" + /* 右旋操作 */ + func rightRotate(node: TreeNode?) -> TreeNode? { + let child = node?.left + let grandChild = child?.right + // 以 child 为原点,将 node 向右旋转 + child?.right = node + node?.left = grandChild + // 更新节点高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋转后子树的根节点 + return child + } + ``` + +=== "JS" + + ```javascript title="avl_tree.js" + /* 右旋操作 */ + #rightRotate(node) { + const child = node.left; + const grandChild = child.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "TS" + + ```typescript title="avl_tree.ts" + /* 右旋操作 */ + rightRotate(node: TreeNode): TreeNode { + const child = node.left; + const grandChild = child.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Dart" + + ```dart title="avl_tree.dart" + /* 右旋操作 */ + TreeNode? rightRotate(TreeNode? node) { + TreeNode? child = node!.left; + TreeNode? grandChild = child!.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Rust" + + ```rust title="avl_tree.rs" + /* 右旋操作 */ + fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().left.clone().unwrap(); + let grand_child = child.borrow().right.clone(); + // 以 child 为原点,将 node 向右旋转 + child.borrow_mut().right = Some(node.clone()); + node.borrow_mut().left = grand_child; + // 更新节点高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋转后子树的根节点 + Some(child) + } + None => None, + } + } + ``` + +=== "C" + + ```c title="avl_tree.c" + /* 右旋操作 */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->left; + grandChild = child->right; + // 以 child 为原点,将 node 向右旋转 + child->right = node; + node->left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Kotlin" + + ```kotlin title="avl_tree.kt" + /* 右旋操作 */ + fun rightRotate(node: TreeNode?): TreeNode { + val child = node!!.left + val grandChild = child!!.right + // 以 child 为原点,将 node 向右旋转 + child.right = node + node.left = grandChild + // 更新节点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } + ``` + +=== "Ruby" + + ```ruby title="avl_tree.rb" + [class]{AVLTree}-[func]{right_rotate} + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 右旋操作 + fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.left; + var grandChild = child.?.right; + // 以 child 为原点,将 node 向右旋转 + child.?.right = node; + node.?.left = grandChild; + // 更新节点高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +### 2.   Left rotation + +Correspondingly, if considering the "mirror" of the above unbalanced binary tree, the "left rotation" operation shown in the Figure 7-28 needs to be performed. + +![Left rotation operation](avl_tree.assets/avltree_left_rotate.png){ class="animation-figure" } + +

Figure 7-28   Left rotation operation

+ +Similarly, as shown in the Figure 7-29 , when the `child` node has a left child (denoted as `grand_child`), a step needs to be added in the left rotation: set `grand_child` as the right child of `node`. + +![Left rotation with grand_child](avl_tree.assets/avltree_left_rotate_with_grandchild.png){ class="animation-figure" } + +

Figure 7-29   Left rotation with grand_child

+ +It can be observed that **the right and left rotation operations are logically symmetrical, and they solve two symmetrical types of imbalance**. Based on symmetry, by replacing all `left` with `right`, and all `right` with `left` in the implementation code of right rotation, we can get the implementation code for left rotation: + +=== "Python" + + ```python title="avl_tree.py" + def left_rotate(self, node: TreeNode | None) -> TreeNode | None: + """左旋操作""" + child = node.right + grand_child = child.left + # 以 child 为原点,将 node 向左旋转 + child.left = node + node.right = grand_child + # 更新节点高度 + self.update_height(node) + self.update_height(child) + # 返回旋转后子树的根节点 + return child + ``` + +=== "C++" + + ```cpp title="avl_tree.cpp" + /* 左旋操作 */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child = node->right; + TreeNode *grandChild = child->left; + // 以 child 为原点,将 node 向左旋转 + child->left = node; + node->right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Java" + + ```java title="avl_tree.java" + /* 左旋操作 */ + TreeNode leftRotate(TreeNode node) { + TreeNode child = node.right; + TreeNode grandChild = child.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 左旋操作 */ + TreeNode? LeftRotate(TreeNode? node) { + TreeNode? child = node?.right; + TreeNode? grandChild = child?.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Go" + + ```go title="avl_tree.go" + /* 左旋操作 */ + func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // 以 child 为原点,将 node 向左旋转 + child.Left = node + node.Right = grandChild + // 更新节点高度 + t.updateHeight(node) + t.updateHeight(child) + // 返回旋转后子树的根节点 + return child + } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" + /* 左旋操作 */ + func leftRotate(node: TreeNode?) -> TreeNode? { + let child = node?.right + let grandChild = child?.left + // 以 child 为原点,将 node 向左旋转 + child?.left = node + node?.right = grandChild + // 更新节点高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋转后子树的根节点 + return child + } + ``` + +=== "JS" + + ```javascript title="avl_tree.js" + /* 左旋操作 */ + #leftRotate(node) { + const child = node.right; + const grandChild = child.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "TS" + + ```typescript title="avl_tree.ts" + /* 左旋操作 */ + leftRotate(node: TreeNode): TreeNode { + const child = node.right; + const grandChild = child.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Dart" + + ```dart title="avl_tree.dart" + /* 左旋操作 */ + TreeNode? leftRotate(TreeNode? node) { + TreeNode? child = node!.right; + TreeNode? grandChild = child!.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Rust" + + ```rust title="avl_tree.rs" + /* 左旋操作 */ + fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().right.clone().unwrap(); + let grand_child = child.borrow().left.clone(); + // 以 child 为原点,将 node 向左旋转 + child.borrow_mut().left = Some(node.clone()); + node.borrow_mut().right = grand_child; + // 更新节点高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋转后子树的根节点 + Some(child) + } + None => None, + } + } + ``` + +=== "C" + + ```c title="avl_tree.c" + /* 左旋操作 */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->right; + grandChild = child->left; + // 以 child 为原点,将 node 向左旋转 + child->left = node; + node->right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Kotlin" + + ```kotlin title="avl_tree.kt" + /* 左旋操作 */ + fun leftRotate(node: TreeNode?): TreeNode { + val child = node!!.right + val grandChild = child!!.left + // 以 child 为原点,将 node 向左旋转 + child.left = node + node.right = grandChild + // 更新节点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } + ``` + +=== "Ruby" + + ```ruby title="avl_tree.rb" + [class]{AVLTree}-[func]{left_rotate} + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 左旋操作 + fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.right; + var grandChild = child.?.left; + // 以 child 为原点,将 node 向左旋转 + child.?.left = node; + node.?.right = grandChild; + // 更新节点高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +### 3.   Right-left rotation + +For the unbalanced node 3 shown in the Figure 7-30 , using either left or right rotation alone cannot restore balance to the subtree. In this case, a "left rotation" needs to be performed on `child` first, followed by a "right rotation" on `node`. + +![Right-left rotation](avl_tree.assets/avltree_left_right_rotate.png){ class="animation-figure" } + +

Figure 7-30   Right-left rotation

+ +### 4.   Left-right rotation + +As shown in the Figure 7-31 , for the mirror case of the above unbalanced binary tree, a "right rotation" needs to be performed on `child` first, followed by a "left rotation" on `node`. + +![Left-right rotation](avl_tree.assets/avltree_right_left_rotate.png){ class="animation-figure" } + +

Figure 7-31   Left-right rotation

+ +### 5.   Choice of rotation + +The four kinds of imbalances shown in the Figure 7-32 correspond to the cases described above, respectively requiring right rotation, left-right rotation, right-left rotation, and left rotation. + +![The four rotation cases of AVL tree](avl_tree.assets/avltree_rotation_cases.png){ class="animation-figure" } + +

Figure 7-32   The four rotation cases of AVL tree

+ +As shown in the Table 7-3 , we determine which of the above cases an unbalanced node belongs to by judging the sign of the balance factor of the unbalanced node and its higher-side child's balance factor. + +

Table 7-3   Conditions for Choosing Among the Four Rotation Cases

+ +
+ +| Balance factor of unbalanced node | Balance factor of child node | Rotation method to use | +| --------------------------------- | ---------------------------- | --------------------------------- | +| $> 1$ (Left-leaning tree) | $\geq 0$ | Right rotation | +| $> 1$ (Left-leaning tree) | $<0$ | Left rotation then right rotation | +| $< -1$ (Right-leaning tree) | $\leq 0$ | Left rotation | +| $< -1$ (Right-leaning tree) | $>0$ | Right rotation then left rotation | + +
+ +For convenience, we encapsulate the rotation operations into a function. **With this function, we can perform rotations on various kinds of imbalances, restoring balance to unbalanced nodes**. The code is as follows: + +=== "Python" + + ```python title="avl_tree.py" + def rotate(self, node: TreeNode | None) -> TreeNode | None: + """执行旋转操作,使该子树重新恢复平衡""" + # 获取节点 node 的平衡因子 + balance_factor = self.balance_factor(node) + # 左偏树 + if balance_factor > 1: + if self.balance_factor(node.left) >= 0: + # 右旋 + return self.right_rotate(node) + else: + # 先左旋后右旋 + node.left = self.left_rotate(node.left) + return self.right_rotate(node) + # 右偏树 + elif balance_factor < -1: + if self.balance_factor(node.right) <= 0: + # 左旋 + return self.left_rotate(node) + else: + # 先右旋后左旋 + node.right = self.right_rotate(node.right) + return self.left_rotate(node) + # 平衡树,无须旋转,直接返回 + return node + ``` + +=== "C++" + + ```cpp title="avl_tree.cpp" + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode *rotate(TreeNode *node) { + // 获取节点 node 的平衡因子 + int _balanceFactor = balanceFactor(node); + // 左偏树 + if (_balanceFactor > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏树 + if (_balanceFactor < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +=== "Java" + + ```java title="avl_tree.java" + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode rotate(TreeNode node) { + // 获取节点 node 的平衡因子 + int balanceFactor = balanceFactor(node); + // 左偏树 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右偏树 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode? Rotate(TreeNode? node) { + // 获取节点 node 的平衡因子 + int balanceFactorInt = BalanceFactor(node); + // 左偏树 + if (balanceFactorInt > 1) { + if (BalanceFactor(node?.left) >= 0) { + // 右旋 + return RightRotate(node); + } else { + // 先左旋后右旋 + node!.left = LeftRotate(node!.left); + return RightRotate(node); + } + } + // 右偏树 + if (balanceFactorInt < -1) { + if (BalanceFactor(node?.right) <= 0) { + // 左旋 + return LeftRotate(node); + } else { + // 先右旋后左旋 + node!.right = RightRotate(node!.right); + return LeftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +=== "Go" + + ```go title="avl_tree.go" + /* 执行旋转操作,使该子树重新恢复平衡 */ + func (t *aVLTree) rotate(node *TreeNode) *TreeNode { + // 获取节点 node 的平衡因子 + // Go 推荐短变量,这里 bf 指代 t.balanceFactor + bf := t.balanceFactor(node) + // 左偏树 + if bf > 1 { + if t.balanceFactor(node.Left) >= 0 { + // 右旋 + return t.rightRotate(node) + } else { + // 先左旋后右旋 + node.Left = t.leftRotate(node.Left) + return t.rightRotate(node) + } + } + // 右偏树 + if bf < -1 { + if t.balanceFactor(node.Right) <= 0 { + // 左旋 + return t.leftRotate(node) + } else { + // 先右旋后左旋 + node.Right = t.rightRotate(node.Right) + return t.leftRotate(node) + } + } + // 平衡树,无须旋转,直接返回 + return node + } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" + /* 执行旋转操作,使该子树重新恢复平衡 */ + func rotate(node: TreeNode?) -> TreeNode? { + // 获取节点 node 的平衡因子 + let balanceFactor = balanceFactor(node: node) + // 左偏树 + if balanceFactor > 1 { + if self.balanceFactor(node: node?.left) >= 0 { + // 右旋 + return rightRotate(node: node) + } else { + // 先左旋后右旋 + node?.left = leftRotate(node: node?.left) + return rightRotate(node: node) + } + } + // 右偏树 + if balanceFactor < -1 { + if self.balanceFactor(node: node?.right) <= 0 { + // 左旋 + return leftRotate(node: node) + } else { + // 先右旋后左旋 + node?.right = rightRotate(node: node?.right) + return leftRotate(node: node) + } + } + // 平衡树,无须旋转,直接返回 + return node + } + ``` + +=== "JS" + + ```javascript title="avl_tree.js" + /* 执行旋转操作,使该子树重新恢复平衡 */ + #rotate(node) { + // 获取节点 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏树 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.#rightRotate(node); + } else { + // 先左旋后右旋 + node.left = this.#leftRotate(node.left); + return this.#rightRotate(node); + } + } + // 右偏树 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.#leftRotate(node); + } else { + // 先右旋后左旋 + node.right = this.#rightRotate(node.right); + return this.#leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +=== "TS" + + ```typescript title="avl_tree.ts" + /* 执行旋转操作,使该子树重新恢复平衡 */ + rotate(node: TreeNode): TreeNode { + // 获取节点 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏树 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.rightRotate(node); + } else { + // 先左旋后右旋 + node.left = this.leftRotate(node.left); + return this.rightRotate(node); + } + } + // 右偏树 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.leftRotate(node); + } else { + // 先右旋后左旋 + node.right = this.rightRotate(node.right); + return this.leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +=== "Dart" + + ```dart title="avl_tree.dart" + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode? rotate(TreeNode? node) { + // 获取节点 node 的平衡因子 + int factor = balanceFactor(node); + // 左偏树 + if (factor > 1) { + if (balanceFactor(node!.left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右偏树 + if (factor < -1) { + if (balanceFactor(node!.right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +=== "Rust" + + ```rust title="avl_tree.rs" + /* 执行旋转操作,使该子树重新恢复平衡 */ + fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + // 获取节点 node 的平衡因子 + let balance_factor = Self::balance_factor(node.clone()); + // 左偏树 + if balance_factor > 1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().left.clone()) >= 0 { + // 右旋 + Self::right_rotate(Some(node)) + } else { + // 先左旋后右旋 + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::left_rotate(left); + Self::right_rotate(Some(node)) + } + } + // 右偏树 + else if balance_factor < -1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().right.clone()) <= 0 { + // 左旋 + Self::left_rotate(Some(node)) + } else { + // 先右旋后左旋 + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::right_rotate(right); + Self::left_rotate(Some(node)) + } + } else { + // 平衡树,无须旋转,直接返回 + node + } + } + ``` + +=== "C" + + ```c title="avl_tree.c" + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode *rotate(TreeNode *node) { + // 获取节点 node 的平衡因子 + int bf = balanceFactor(node); + // 左偏树 + if (bf > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏树 + if (bf < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="avl_tree.kt" + /* 执行旋转操作,使该子树重新恢复平衡 */ + fun rotate(node: TreeNode): TreeNode { + // 获取节点 node 的平衡因子 + val balanceFactor = balanceFactor(node) + // 左偏树 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右旋 + return rightRotate(node) + } else { + // 先左旋后右旋 + node.left = leftRotate(node.left) + return rightRotate(node) + } + } + // 右偏树 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左旋 + return leftRotate(node) + } else { + // 先右旋后左旋 + node.right = rightRotate(node.right) + return leftRotate(node) + } + } + // 平衡树,无须旋转,直接返回 + return node + } + ``` + +=== "Ruby" + + ```ruby title="avl_tree.rb" + [class]{AVLTree}-[func]{rotate} + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 执行旋转操作,使该子树重新恢复平衡 + fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + // 获取节点 node 的平衡因子 + var balance_factor = self.balanceFactor(node); + // 左偏树 + if (balance_factor > 1) { + if (self.balanceFactor(node.?.left) >= 0) { + // 右旋 + return self.rightRotate(node); + } else { + // 先左旋后右旋 + node.?.left = self.leftRotate(node.?.left); + return self.rightRotate(node); + } + } + // 右偏树 + if (balance_factor < -1) { + if (self.balanceFactor(node.?.right) <= 0) { + // 左旋 + return self.leftRotate(node); + } else { + // 先右旋后左旋 + node.?.right = self.rightRotate(node.?.right); + return self.leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + +## 7.5.3   Common operations in AVL trees + +### 1.   Node insertion + +The node insertion operation in AVL trees is similar to that in binary search trees. The only difference is that after inserting a node in an AVL tree, a series of unbalanced nodes may appear along the path from that node to the root node. Therefore, **we need to start from this node and perform rotation operations upwards to restore balance to all unbalanced nodes**. The code is as follows: + +=== "Python" + + ```python title="avl_tree.py" + def insert(self, val): + """插入节点""" + self._root = self.insert_helper(self._root, val) + + def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: + """递归插入节点(辅助方法)""" + if node is None: + return TreeNode(val) + # 1. 查找插入位置并插入节点 + if val < node.val: + node.left = self.insert_helper(node.left, val) + elif val > node.val: + node.right = self.insert_helper(node.right, val) + else: + # 重复节点不插入,直接返回 + return node + # 更新节点高度 + self.update_height(node) + # 2. 执行旋转操作,使该子树重新恢复平衡 + return self.rotate(node) + ``` + +=== "C++" + + ```cpp title="avl_tree.cpp" + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 递归插入节点(辅助方法) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == nullptr) + return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node->val) + node->left = insertHelper(node->left, val); + else if (val > node->val) + node->right = insertHelper(node->right, val); + else + return node; // 重复节点不插入,直接返回 + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Java" + + ```java title="avl_tree.java" + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 递归插入节点(辅助方法) */ + TreeNode insertHelper(TreeNode node, int val) { + if (node == null) + return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重复节点不插入,直接返回 + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 插入节点 */ + void Insert(int val) { + root = InsertHelper(root, val); + } + + /* 递归插入节点(辅助方法) */ + TreeNode? InsertHelper(TreeNode? node, int val) { + if (node == null) return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) + node.left = InsertHelper(node.left, val); + else if (val > node.val) + node.right = InsertHelper(node.right, val); + else + return node; // 重复节点不插入,直接返回 + UpdateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Go" + + ```go title="avl_tree.go" + /* 插入节点 */ + func (t *aVLTree) insert(val int) { + t.root = t.insertHelper(t.root, val) + } + + /* 递归插入节点(辅助函数) */ + func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return NewTreeNode(val) + } + /* 1. 查找插入位置并插入节点 */ + if val < node.Val.(int) { + node.Left = t.insertHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.insertHelper(node.Right, val) + } else { + // 重复节点不插入,直接返回 + return node + } + // 更新节点高度 + t.updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = t.rotate(node) + // 返回子树的根节点 + return node + } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" + /* 插入节点 */ + func insert(val: Int) { + root = insertHelper(node: root, val: val) + } + + /* 递归插入节点(辅助方法) */ + func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return TreeNode(x: val) + } + /* 1. 查找插入位置并插入节点 */ + if val < node!.val { + node?.left = insertHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = insertHelper(node: node?.right, val: val) + } else { + return node // 重复节点不插入,直接返回 + } + updateHeight(node: node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node: node) + // 返回子树的根节点 + return node + } + ``` + +=== "JS" + + ```javascript title="avl_tree.js" + /* 插入节点 */ + insert(val) { + this.root = this.#insertHelper(this.root, val); + } + + /* 递归插入节点(辅助方法) */ + #insertHelper(node, val) { + if (node === null) return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) node.left = this.#insertHelper(node.left, val); + else if (val > node.val) + node.right = this.#insertHelper(node.right, val); + else return node; // 重复节点不插入,直接返回 + this.#updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.#rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "TS" + + ```typescript title="avl_tree.ts" + /* 插入节点 */ + insert(val: number): void { + this.root = this.insertHelper(this.root, val); + } + + /* 递归插入节点(辅助方法) */ + insertHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) { + node.left = this.insertHelper(node.left, val); + } else if (val > node.val) { + node.right = this.insertHelper(node.right, val); + } else { + return node; // 重复节点不插入,直接返回 + } + this.updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Dart" + + ```dart title="avl_tree.dart" + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 递归插入节点(辅助方法) */ + TreeNode? insertHelper(TreeNode? node, int val) { + if (node == null) return TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重复节点不插入,直接返回 + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Rust" + + ```rust title="avl_tree.rs" + /* 插入节点 */ + fn insert(&mut self, val: i32) { + self.root = Self::insert_helper(self.root.clone(), val); + } + + /* 递归插入节点(辅助方法) */ + fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查找插入位置并插入节点 */ + match { + let node_val = node.borrow().val; + node_val + } + .cmp(&val) + { + Ordering::Greater => { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::insert_helper(left, val); + } + Ordering::Less => { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::insert_helper(right, val); + } + Ordering::Equal => { + return Some(node); // 重复节点不插入,直接返回 + } + } + Self::update_height(Some(node.clone())); // 更新节点高度 + + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子树的根节点 + Some(node) + } + None => Some(TreeNode::new(val)), + } + } + ``` + +=== "C" + + ```c title="avl_tree.c" + /* 插入节点 */ + void insert(AVLTree *tree, int val) { + tree->root = insertHelper(tree->root, val); + } + + /* 递归插入节点(辅助函数) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == NULL) { + return newTreeNode(val); + } + /* 1. 查找插入位置并插入节点 */ + if (val < node->val) { + node->left = insertHelper(node->left, val); + } else if (val > node->val) { + node->right = insertHelper(node->right, val); + } else { + // 重复节点不插入,直接返回 + return node; + } + // 更新节点高度 + updateHeight(node); + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="avl_tree.kt" + /* 插入节点 */ + fun insert(value: Int) { + root = insertHelper(root, value) + } + + /* 递归插入节点(辅助方法) */ + fun insertHelper(n: TreeNode?, value: Int): TreeNode { + if (n == null) + return TreeNode(value) + var node = n + /* 1. 查找插入位置并插入节点 */ + if (value < node.value) node.left = insertHelper(node.left, value) + else if (value > node.value) node.right = insertHelper(node.right, value) + else return node // 重复节点不插入,直接返回 + + updateHeight(node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node + } + ``` + +=== "Ruby" + + ```ruby title="avl_tree.rb" + [class]{AVLTree}-[func]{insert} + + [class]{AVLTree}-[func]{insert_helper} + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 插入节点 + fn insert(self: *Self, val: T) !void { + self.root = (try self.insertHelper(self.root, val)).?; + } + + // 递归插入节点(辅助方法) + fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { + var node = node_; + if (node == null) { + var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); + tmp_node.init(val); + return tmp_node; + } + // 1. 查找插入位置并插入节点 + if (val < node.?.val) { + node.?.left = try self.insertHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = try self.insertHelper(node.?.right, val); + } else { + return node; // 重复节点不插入,直接返回 + } + self.updateHeight(node); // 更新节点高度 + // 2. 执行旋转操作,使该子树重新恢复平衡 + node = self.rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +### 2.   Node removal + +Similarly, based on the method of removing nodes in binary search trees, rotation operations need to be performed from the bottom up to restore balance to all unbalanced nodes. The code is as follows: + +=== "Python" + + ```python title="avl_tree.py" + def remove(self, val: int): + """删除节点""" + self._root = self.remove_helper(self._root, val) + + def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: + """递归删除节点(辅助方法)""" + if node is None: + return None + # 1. 查找节点并删除 + if val < node.val: + node.left = self.remove_helper(node.left, val) + elif val > node.val: + node.right = self.remove_helper(node.right, val) + else: + if node.left is None or node.right is None: + child = node.left or node.right + # 子节点数量 = 0 ,直接删除 node 并返回 + if child is None: + return None + # 子节点数量 = 1 ,直接删除 node + else: + node = child + else: + # 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + temp = node.right + while temp.left is not None: + temp = temp.left + node.right = self.remove_helper(node.right, temp.val) + node.val = temp.val + # 更新节点高度 + self.update_height(node) + # 2. 执行旋转操作,使该子树重新恢复平衡 + return self.rotate(node) + ``` + +=== "C++" + + ```cpp title="avl_tree.cpp" + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 递归删除节点(辅助方法) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 1. 查找节点并删除 */ + if (val < node->val) + node->left = removeHelper(node->left, val); + else if (val > node->val) + node->right = removeHelper(node->right, val); + else { + if (node->left == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == nullptr) { + delete node; + return nullptr; + } + // 子节点数量 = 1 ,直接删除 node + else { + delete node; + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode *temp = node->right; + while (temp->left != nullptr) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Java" + + ```java title="avl_tree.java" + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 递归删除节点(辅助方法) */ + TreeNode removeHelper(TreeNode node, int val) { + if (node == null) + return null; + /* 1. 查找节点并删除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode child = node.left != null ? node.left : node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null; + // 子节点数量 = 1 ,直接删除 node + else + node = child; + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 删除节点 */ + void Remove(int val) { + root = RemoveHelper(root, val); + } + + /* 递归删除节点(辅助方法) */ + TreeNode? RemoveHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) + node.left = RemoveHelper(node.left, val); + else if (val > node.val) + node.right = RemoveHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null; + // 子节点数量 = 1 ,直接删除 node + else + node = child; + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode? temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = RemoveHelper(node.right, temp.val!.Value); + node.val = temp.val; + } + } + UpdateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Go" + + ```go title="avl_tree.go" + /* 删除节点 */ + func (t *aVLTree) remove(val int) { + t.root = t.removeHelper(t.root, val) + } + + /* 递归删除节点(辅助函数) */ + func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 1. 查找节点并删除 */ + if val < node.Val.(int) { + node.Left = t.removeHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.removeHelper(node.Right, val) + } else { + if node.Left == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + if child == nil { + // 子节点数量 = 0 ,直接删除 node 并返回 + return nil + } else { + // 子节点数量 = 1 ,直接删除 node + node = child + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + temp := node.Right + for temp.Left != nil { + temp = temp.Left + } + node.Right = t.removeHelper(node.Right, temp.Val.(int)) + node.Val = temp.Val + } + } + // 更新节点高度 + t.updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = t.rotate(node) + // 返回子树的根节点 + return node + } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" + /* 删除节点 */ + func remove(val: Int) { + root = removeHelper(node: root, val: val) + } + + /* 递归删除节点(辅助方法) */ + func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return nil + } + /* 1. 查找节点并删除 */ + if val < node!.val { + node?.left = removeHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = removeHelper(node: node?.right, val: val) + } else { + if node?.left == nil || node?.right == nil { + let child = node?.left ?? node?.right + // 子节点数量 = 0 ,直接删除 node 并返回 + if child == nil { + return nil + } + // 子节点数量 = 1 ,直接删除 node + else { + node = child + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node?.right + while temp?.left != nil { + temp = temp?.left + } + node?.right = removeHelper(node: node?.right, val: temp!.val) + node?.val = temp!.val + } + } + updateHeight(node: node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node: node) + // 返回子树的根节点 + return node + } + ``` + +=== "JS" + + ```javascript title="avl_tree.js" + /* 删除节点 */ + remove(val) { + this.root = this.#removeHelper(this.root, val); + } + + /* 递归删除节点(辅助方法) */ + #removeHelper(node, val) { + if (node === null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) node.left = this.#removeHelper(node.left, val); + else if (val > node.val) + node.right = this.#removeHelper(node.right, val); + else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child === null) return null; + // 子节点数量 = 1 ,直接删除 node + else node = child; + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.#removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.#updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.#rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "TS" + + ```typescript title="avl_tree.ts" + /* 删除节点 */ + remove(val: number): void { + this.root = this.removeHelper(this.root, val); + } + + /* 递归删除节点(辅助方法) */ + removeHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) { + node.left = this.removeHelper(node.left, val); + } else if (val > node.val) { + node.right = this.removeHelper(node.right, val); + } else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child === null) { + return null; + } else { + // 子节点数量 = 1 ,直接删除 node + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Dart" + + ```dart title="avl_tree.dart" + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 递归删除节点(辅助方法) */ + TreeNode? removeHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null; + // 子节点数量 = 1 ,直接删除 node + else + node = child; + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode? temp = node.right; + while (temp!.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Rust" + + ```rust title="avl_tree.rs" + /* 删除节点 */ + fn remove(&self, val: i32) { + Self::remove_helper(self.root.clone(), val); + } + + /* 递归删除节点(辅助方法) */ + fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查找节点并删除 */ + if val < node.borrow().val { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::remove_helper(left, val); + } else if val > node.borrow().val { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, val); + } else if node.borrow().left.is_none() || node.borrow().right.is_none() { + let child = if node.borrow().left.is_some() { + node.borrow().left.clone() + } else { + node.borrow().right.clone() + }; + match child { + // 子节点数量 = 0 ,直接删除 node 并返回 + None => { + return None; + } + // 子节点数量 = 1 ,直接删除 node + Some(child) => node = child, + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + let mut temp = node.borrow().right.clone().unwrap(); + loop { + let temp_left = temp.borrow().left.clone(); + if temp_left.is_none() { + break; + } + temp = temp_left.unwrap(); + } + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); + node.borrow_mut().val = temp.borrow().val; + } + Self::update_height(Some(node.clone())); // 更新节点高度 + + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子树的根节点 + Some(node) + } + None => None, + } + } + ``` + +=== "C" + + ```c title="avl_tree.c" + /* 删除节点 */ + // 由于引入了 stdio.h ,此处无法使用 remove 关键词 + void removeItem(AVLTree *tree, int val) { + TreeNode *root = removeHelper(tree->root, val); + } + + /* 递归删除节点(辅助函数) */ + TreeNode *removeHelper(TreeNode *node, int val) { + TreeNode *child, *grandChild; + if (node == NULL) { + return NULL; + } + /* 1. 查找节点并删除 */ + if (val < node->val) { + node->left = removeHelper(node->left, val); + } else if (val > node->val) { + node->right = removeHelper(node->right, val); + } else { + if (node->left == NULL || node->right == NULL) { + child = node->left; + if (node->right != NULL) { + child = node->right; + } + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == NULL) { + return NULL; + } else { + // 子节点数量 = 1 ,直接删除 node + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode *temp = node->right; + while (temp->left != NULL) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + // 更新节点高度 + updateHeight(node); + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="avl_tree.kt" + /* 删除节点 */ + fun remove(value: Int) { + root = removeHelper(root, value) + } + + /* 递归删除节点(辅助方法) */ + fun removeHelper(n: TreeNode?, value: Int): TreeNode? { + var node = n ?: return null + /* 1. 查找节点并删除 */ + if (value < node.value) node.left = removeHelper(node.left, value) + else if (value > node.value) node.right = removeHelper(node.right, value) + else { + if (node.left == null || node.right == null) { + val child = if (node.left != null) node.left else node.right + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) return null + else node = child + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node.right + while (temp!!.left != null) { + temp = temp.left + } + node.right = removeHelper(node.right, temp.value) + node.value = temp.value + } + } + updateHeight(node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node + } + ``` + +=== "Ruby" + + ```ruby title="avl_tree.rb" + [class]{AVLTree}-[func]{remove} + + [class]{AVLTree}-[func]{remove_helper} + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 删除节点 + fn remove(self: *Self, val: T) void { + self.root = self.removeHelper(self.root, val).?; + } + + // 递归删除节点(辅助方法) + fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { + var node = node_; + if (node == null) return null; + // 1. 查找节点并删除 + if (val < node.?.val) { + node.?.left = self.removeHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = self.removeHelper(node.?.right, val); + } else { + if (node.?.left == null or node.?.right == null) { + var child = if (node.?.left != null) node.?.left else node.?.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) { + return null; + // 子节点数量 = 1 ,直接删除 node + } else { + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node.?.right; + while (temp.?.left != null) { + temp = temp.?.left; + } + node.?.right = self.removeHelper(node.?.right, temp.?.val); + node.?.val = temp.?.val; + } + } + self.updateHeight(node); // 更新节点高度 + // 2. 执行旋转操作,使该子树重新恢复平衡 + node = self.rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +### 3.   Node search + +The node search operation in AVL trees is consistent with that in binary search trees and will not be detailed here. + +## 7.5.4   Typical applications of AVL trees + +- Organizing and storing large amounts of data, suitable for scenarios with high-frequency searches and low-frequency intertions and removals. +- Used to build index systems in databases. +- Red-black trees are also a common type of balanced binary search tree. Compared to AVL trees, red-black trees have more relaxed balancing conditions, require fewer rotations for node insertion and removal, and have a higher average efficiency for node addition and removal operations. diff --git a/en/docs/chapter_tree/binary_search_tree.md b/en/docs/chapter_tree/binary_search_tree.md new file mode 100755 index 000000000..6032cec08 --- /dev/null +++ b/en/docs/chapter_tree/binary_search_tree.md @@ -0,0 +1,1628 @@ +--- +comments: true +--- + +# 7.4   Binary search tree + +As shown in the Figure 7-16 , a "binary search tree" satisfies the following conditions. + +1. For the root node, the value of all nodes in the left subtree < the value of the root node < the value of all nodes in the right subtree. +2. The left and right subtrees of any node are also binary search trees, i.e., they satisfy condition `1.` as well. + +![Binary search tree](binary_search_tree.assets/binary_search_tree.png){ class="animation-figure" } + +

Figure 7-16   Binary search tree

+ +## 7.4.1   Operations on a binary search tree + +We encapsulate the binary search tree as a class `BinarySearchTree` and declare a member variable `root`, pointing to the tree's root node. + +### 1.   Searching for a node + +Given a target node value `num`, one can search according to the properties of the binary search tree. As shown in the Figure 7-17 , we declare a node `cur` and start from the binary tree's root node `root`, looping to compare the size relationship between the node value `cur.val` and `num`. + +- If `cur.val < num`, it means the target node is in `cur`'s right subtree, thus execute `cur = cur.right`. +- If `cur.val > num`, it means the target node is in `cur`'s left subtree, thus execute `cur = cur.left`. +- If `cur.val = num`, it means the target node is found, exit the loop and return the node. + +=== "<1>" + ![Example of searching for a node in a binary search tree](binary_search_tree.assets/bst_search_step1.png){ class="animation-figure" } + +=== "<2>" + ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png){ class="animation-figure" } + +=== "<3>" + ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png){ class="animation-figure" } + +=== "<4>" + ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png){ class="animation-figure" } + +

Figure 7-17   Example of searching for a node in a binary search tree

+ +The search operation in a binary search tree works on the same principle as the binary search algorithm, eliminating half of the possibilities in each round. The number of loops is at most the height of the binary tree. When the binary tree is balanced, it uses $O(\log n)$ time. Example code is as follows: + +=== "Python" + + ```python title="binary_search_tree.py" + def search(self, num: int) -> TreeNode | None: + """查找节点""" + cur = self._root + # 循环查找,越过叶节点后跳出 + while cur is not None: + # 目标节点在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 目标节点在 cur 的左子树中 + elif cur.val > num: + cur = cur.left + # 找到目标节点,跳出循环 + else: + break + return cur + ``` + +=== "C++" + + ```cpp title="binary_search_tree.cpp" + /* 查找节点 */ + TreeNode *search(int num) { + TreeNode *cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 目标节点在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 目标节点在 cur 的左子树中 + else if (cur->val > num) + cur = cur->left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + ``` + +=== "Java" + + ```java title="binary_search_tree.java" + /* 查找节点 */ + TreeNode search(int num) { + TreeNode cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) + cur = cur.left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + ``` + +=== "C#" + + ```csharp title="binary_search_tree.cs" + /* 查找节点 */ + TreeNode? Search(int num) { + TreeNode? cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.val < num) cur = + cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) + cur = cur.left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + ``` + +=== "Go" + + ```go title="binary_search_tree.go" + /* 查找节点 */ + func (bst *binarySearchTree) search(num int) *TreeNode { + node := bst.root + // 循环查找,越过叶节点后跳出 + for node != nil { + if node.Val.(int) < num { + // 目标节点在 cur 的右子树中 + node = node.Right + } else if node.Val.(int) > num { + // 目标节点在 cur 的左子树中 + node = node.Left + } else { + // 找到目标节点,跳出循环 + break + } + } + // 返回目标节点 + return node + } + ``` + +=== "Swift" + + ```swift title="binary_search_tree.swift" + /* 查找节点 */ + func search(num: Int) -> TreeNode? { + var cur = root + // 循环查找,越过叶节点后跳出 + while cur != nil { + // 目标节点在 cur 的右子树中 + if cur!.val < num { + cur = cur?.right + } + // 目标节点在 cur 的左子树中 + else if cur!.val > num { + cur = cur?.left + } + // 找到目标节点,跳出循环 + else { + break + } + } + // 返回目标节点 + return cur + } + ``` + +=== "JS" + + ```javascript title="binary_search_tree.js" + /* 查找节点 */ + search(num) { + let cur = this.root; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 目标节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) cur = cur.left; + // 找到目标节点,跳出循环 + else break; + } + // 返回目标节点 + return cur; + } + ``` + +=== "TS" + + ```typescript title="binary_search_tree.ts" + /* 查找节点 */ + search(num: number): TreeNode | null { + let cur = this.root; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 目标节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) cur = cur.left; + // 找到目标节点,跳出循环 + else break; + } + // 返回目标节点 + return cur; + } + ``` + +=== "Dart" + + ```dart title="binary_search_tree.dart" + /* 查找节点 */ + TreeNode? search(int _num) { + TreeNode? cur = _root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.val < _num) + cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > _num) + cur = cur.left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + ``` + +=== "Rust" + + ```rust title="binary_search_tree.rs" + /* 查找节点 */ + pub fn search(&self, num: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // 循环查找,越过叶节点后跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 目标节点在 cur 的右子树中 + Ordering::Greater => cur = node.borrow().right.clone(), + // 目标节点在 cur 的左子树中 + Ordering::Less => cur = node.borrow().left.clone(), + // 找到目标节点,跳出循环 + Ordering::Equal => break, + } + } + + // 返回目标节点 + cur + } + ``` + +=== "C" + + ```c title="binary_search_tree.c" + /* 查找节点 */ + TreeNode *search(BinarySearchTree *bst, int num) { + TreeNode *cur = bst->root; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + if (cur->val < num) { + // 目标节点在 cur 的右子树中 + cur = cur->right; + } else if (cur->val > num) { + // 目标节点在 cur 的左子树中 + cur = cur->left; + } else { + // 找到目标节点,跳出循环 + break; + } + } + // 返回目标节点 + return cur; + } + ``` + +=== "Kotlin" + + ```kotlin title="binary_search_tree.kt" + /* 查找节点 */ + fun search(num: Int): TreeNode? { + var cur = root + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + cur = if (cur.value < num) cur.right + // 目标节点在 cur 的左子树中 + else if (cur.value > num) cur.left + // 找到目标节点,跳出循环 + else break + } + // 返回目标节点 + return cur + } + ``` + +=== "Ruby" + + ```ruby title="binary_search_tree.rb" + [class]{BinarySearchTree}-[func]{search} + ``` + +=== "Zig" + + ```zig title="binary_search_tree.zig" + // 查找节点 + fn search(self: *Self, num: T) ?*inc.TreeNode(T) { + var cur = self.root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.?.val < num) { + cur = cur.?.right; + // 目标节点在 cur 的左子树中 + } else if (cur.?.val > num) { + cur = cur.?.left; + // 找到目标节点,跳出循环 + } else { + break; + } + } + // 返回目标节点 + return cur; + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +### 2.   Inserting a node + +Given an element `num` to be inserted, to maintain the property of the binary search tree "left subtree < root node < right subtree," the insertion operation proceeds as shown in the Figure 7-18 . + +1. **Finding the insertion position**: Similar to the search operation, start from the root node and loop downwards according to the size relationship between the current node value and `num` until passing through the leaf node (traversing to `None`) then exit the loop. +2. **Insert the node at that position**: Initialize the node `num` and place it where `None` was. + +![Inserting a node into a binary search tree](binary_search_tree.assets/bst_insert.png){ class="animation-figure" } + +

Figure 7-18   Inserting a node into a binary search tree

+ +In the code implementation, note the following two points. + +- The binary search tree does not allow duplicate nodes; otherwise, it will violate its definition. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed, and it directly returns. +- To perform the insertion operation, we need to use the node `pre` to save the node from the last loop. This way, when traversing to `None`, we can get its parent node, thus completing the node insertion operation. + +=== "Python" + + ```python title="binary_search_tree.py" + def insert(self, num: int): + """插入节点""" + # 若树为空,则初始化根节点 + if self._root is None: + self._root = TreeNode(num) + return + # 循环查找,越过叶节点后跳出 + cur, pre = self._root, None + while cur is not None: + # 找到重复节点,直接返回 + if cur.val == num: + return + pre = cur + # 插入位置在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 插入位置在 cur 的左子树中 + else: + cur = cur.left + # 插入节点 + node = TreeNode(num) + if pre.val < num: + pre.right = node + else: + pre.left = node + ``` + +=== "C++" + + ```cpp title="binary_search_tree.cpp" + /* 插入节点 */ + void insert(int num) { + // 若树为空,则初始化根节点 + if (root == nullptr) { + root = new TreeNode(num); + return; + } + TreeNode *cur = root, *pre = nullptr; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 找到重复节点,直接返回 + if (cur->val == num) + return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 插入位置在 cur 的左子树中 + else + cur = cur->left; + } + // 插入节点 + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; + } + ``` + +=== "Java" + + ```java title="binary_search_tree.java" + /* 插入节点 */ + void insert(int num) { + // 若树为空,则初始化根节点 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; + } + // 插入节点 + TreeNode node = new TreeNode(num); + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + ``` + +=== "C#" + + ```csharp title="binary_search_tree.cs" + /* 插入节点 */ + void Insert(int num) { + // 若树为空,则初始化根节点 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode? cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; + } + + // 插入节点 + TreeNode node = new(num); + if (pre != null) { + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + } + ``` + +=== "Go" + + ```go title="binary_search_tree.go" + /* 插入节点 */ + func (bst *binarySearchTree) insert(num int) { + cur := bst.root + // 若树为空,则初始化根节点 + if cur == nil { + bst.root = NewTreeNode(num) + return + } + // 待插入节点之前的节点位置 + var pre *TreeNode = nil + // 循环查找,越过叶节点后跳出 + for cur != nil { + if cur.Val == num { + return + } + pre = cur + if cur.Val.(int) < num { + cur = cur.Right + } else { + cur = cur.Left + } + } + // 插入节点 + node := NewTreeNode(num) + if pre.Val.(int) < num { + pre.Right = node + } else { + pre.Left = node + } + } + ``` + +=== "Swift" + + ```swift title="binary_search_tree.swift" + /* 插入节点 */ + func insert(num: Int) { + // 若树为空,则初始化根节点 + if root == nil { + root = TreeNode(x: num) + return + } + var cur = root + var pre: TreeNode? + // 循环查找,越过叶节点后跳出 + while cur != nil { + // 找到重复节点,直接返回 + if cur!.val == num { + return + } + pre = cur + // 插入位置在 cur 的右子树中 + if cur!.val < num { + cur = cur?.right + } + // 插入位置在 cur 的左子树中 + else { + cur = cur?.left + } + } + // 插入节点 + let node = TreeNode(x: num) + if pre!.val < num { + pre?.right = node + } else { + pre?.left = node + } + } + ``` + +=== "JS" + + ```javascript title="binary_search_tree.js" + /* 插入节点 */ + insert(num) { + // 若树为空,则初始化根节点 + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur = this.root, + pre = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到重复节点,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子树中 + else cur = cur.left; + } + // 插入节点 + const node = new TreeNode(num); + if (pre.val < num) pre.right = node; + else pre.left = node; + } + ``` + +=== "TS" + + ```typescript title="binary_search_tree.ts" + /* 插入节点 */ + insert(num: number): void { + // 若树为空,则初始化根节点 + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到重复节点,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子树中 + else cur = cur.left; + } + // 插入节点 + const node = new TreeNode(num); + if (pre!.val < num) pre!.right = node; + else pre!.left = node; + } + ``` + +=== "Dart" + + ```dart title="binary_search_tree.dart" + /* 插入节点 */ + void insert(int _num) { + // 若树为空,则初始化根节点 + if (_root == null) { + _root = TreeNode(_num); + return; + } + TreeNode? cur = _root; + TreeNode? pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.val == _num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < _num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; + } + // 插入节点 + TreeNode? node = TreeNode(_num); + if (pre!.val < _num) + pre.right = node; + else + pre.left = node; + } + ``` + +=== "Rust" + + ```rust title="binary_search_tree.rs" + /* 插入节点 */ + pub fn insert(&mut self, num: i32) { + // 若树为空,则初始化根节点 + if self.root.is_none() { + self.root = Some(TreeNode::new(num)); + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 循环查找,越过叶节点后跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到重复节点,直接返回 + Ordering::Equal => return, + // 插入位置在 cur 的右子树中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 插入位置在 cur 的左子树中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 插入节点 + let pre = pre.unwrap(); + let node = Some(TreeNode::new(num)); + if num > pre.borrow().val { + pre.borrow_mut().right = node; + } else { + pre.borrow_mut().left = node; + } + } + ``` + +=== "C" + + ```c title="binary_search_tree.c" + /* 插入节点 */ + void insert(BinarySearchTree *bst, int num) { + // 若树为空,则初始化根节点 + if (bst->root == NULL) { + bst->root = newTreeNode(num); + return; + } + TreeNode *cur = bst->root, *pre = NULL; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + // 找到重复节点,直接返回 + if (cur->val == num) { + return; + } + pre = cur; + if (cur->val < num) { + // 插入位置在 cur 的右子树中 + cur = cur->right; + } else { + // 插入位置在 cur 的左子树中 + cur = cur->left; + } + } + // 插入节点 + TreeNode *node = newTreeNode(num); + if (pre->val < num) { + pre->right = node; + } else { + pre->left = node; + } + } + ``` + +=== "Kotlin" + + ```kotlin title="binary_search_tree.kt" + /* 插入节点 */ + fun insert(num: Int) { + // 若树为空,则初始化根节点 + if (root == null) { + root = TreeNode(num) + return + } + var cur = root + var pre: TreeNode? = null + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.value == num) return + pre = cur + // 插入位置在 cur 的右子树中 + cur = if (cur.value < num) cur.right + // 插入位置在 cur 的左子树中 + else cur.left + } + // 插入节点 + val node = TreeNode(num) + if (pre?.value!! < num) pre.right = node + else pre.left = node + } + ``` + +=== "Ruby" + + ```ruby title="binary_search_tree.rb" + [class]{BinarySearchTree}-[func]{insert} + ``` + +=== "Zig" + + ```zig title="binary_search_tree.zig" + // 插入节点 + fn insert(self: *Self, num: T) !void { + // 若树为空,则初始化根节点 + if (self.root == null) { + self.root = try self.mem_allocator.create(inc.TreeNode(T)); + return; + } + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.?.val == num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.?.val < num) { + cur = cur.?.right; + // 插入位置在 cur 的左子树中 + } else { + cur = cur.?.left; + } + } + // 插入节点 + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(num); + if (pre.?.val < num) { + pre.?.right = node; + } else { + pre.?.left = node; + } + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +Similar to searching for a node, inserting a node uses $O(\log n)$ time. + +### 3.   Removing a node + +First, find the target node in the binary tree, then remove it. Similar to inserting a node, we need to ensure that after the removal operation is completed, the property of the binary search tree "left subtree < root node < right subtree" is still satisfied. Therefore, based on the number of child nodes of the target node, we divide it into 0, 1, and 2 cases, performing the corresponding node removal operations. + +As shown in the Figure 7-19 , when the degree of the node to be removed is $0$, it means the node is a leaf node, and it can be directly removed. + +![Removing a node in a binary search tree (degree 0)](binary_search_tree.assets/bst_remove_case1.png){ class="animation-figure" } + +

Figure 7-19   Removing a node in a binary search tree (degree 0)

+ +As shown in the Figure 7-20 , when the degree of the node to be removed is $1$, replacing the node to be removed with its child node is sufficient. + +![Removing a node in a binary search tree (degree 1)](binary_search_tree.assets/bst_remove_case2.png){ class="animation-figure" } + +

Figure 7-20   Removing a node in a binary search tree (degree 1)

+ +When the degree of the node to be removed is $2$, we cannot remove it directly, but need to use a node to replace it. To maintain the property of the binary search tree "left subtree < root node < right subtree," **this node can be either the smallest node of the right subtree or the largest node of the left subtree**. + +Assuming we choose the smallest node of the right subtree (the next node in in-order traversal), then the removal operation proceeds as shown in the Figure 7-21 . + +1. Find the next node in the "in-order traversal sequence" of the node to be removed, denoted as `tmp`. +2. Replace the value of the node to be removed with `tmp`'s value, and recursively remove the node `tmp` in the tree. + +=== "<1>" + ![Removing a node in a binary search tree (degree 2)](binary_search_tree.assets/bst_remove_case3_step1.png){ class="animation-figure" } + +=== "<2>" + ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png){ class="animation-figure" } + +=== "<3>" + ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png){ class="animation-figure" } + +=== "<4>" + ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png){ class="animation-figure" } + +

Figure 7-21   Removing a node in a binary search tree (degree 2)

+ +The operation of removing a node also uses $O(\log n)$ time, where finding the node to be removed requires $O(\log n)$ time, and obtaining the in-order traversal successor node requires $O(\log n)$ time. Example code is as follows: + +=== "Python" + + ```python title="binary_search_tree.py" + def remove(self, num: int): + """删除节点""" + # 若树为空,直接提前返回 + if self._root is None: + return + # 循环查找,越过叶节点后跳出 + cur, pre = self._root, None + while cur is not None: + # 找到待删除节点,跳出循环 + if cur.val == num: + break + pre = cur + # 待删除节点在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 待删除节点在 cur 的左子树中 + else: + cur = cur.left + # 若无待删除节点,则直接返回 + if cur is None: + return + + # 子节点数量 = 0 or 1 + if cur.left is None or cur.right is None: + # 当子节点数量 = 0 / 1 时, child = null / 该子节点 + child = cur.left or cur.right + # 删除节点 cur + if cur != self._root: + if pre.left == cur: + pre.left = child + else: + pre.right = child + else: + # 若删除节点为根节点,则重新指定根节点 + self._root = child + # 子节点数量 = 2 + else: + # 获取中序遍历中 cur 的下一个节点 + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # 递归删除节点 tmp + self.remove(tmp.val) + # 用 tmp 覆盖 cur + cur.val = tmp.val + ``` + +=== "C++" + + ```cpp title="binary_search_tree.cpp" + /* 删除节点 */ + void remove(int num) { + // 若树为空,直接提前返回 + if (root == nullptr) + return; + TreeNode *cur = root, *pre = nullptr; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 找到待删除节点,跳出循环 + if (cur->val == num) + break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 待删除节点在 cur 的左子树中 + else + cur = cur->left; + } + // 若无待删除节点,则直接返回 + if (cur == nullptr) + return; + // 子节点数量 = 0 or 1 + if (cur->left == nullptr || cur->right == nullptr) { + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // 删除节点 cur + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } + // 释放内存 + delete cur; + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 递归删除节点 tmp + remove(tmp->val); + // 用 tmp 覆盖 cur + cur->val = tmpVal; + } + } + ``` + +=== "Java" + + ```java title="binary_search_tree.java" + /* 删除节点 */ + void remove(int num) { + // 若树为空,直接提前返回 + if (root == null) + return; + TreeNode cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.val == num) + break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 待删除节点在 cur 的左子树中 + else + cur = cur.left; + } + // 若无待删除节点,则直接返回 + if (cur == null) + return; + // 子节点数量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + TreeNode child = cur.left != null ? cur.left : cur.right; + // 删除节点 cur + if (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + TreeNode tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } + } + ``` + +=== "C#" + + ```csharp title="binary_search_tree.cs" + /* 删除节点 */ + void Remove(int num) { + // 若树为空,直接提前返回 + if (root == null) + return; + TreeNode? cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.val == num) + break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 待删除节点在 cur 的左子树中 + else + cur = cur.left; + } + // 若无待删除节点,则直接返回 + if (cur == null) + return; + // 子节点数量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + TreeNode? child = cur.left ?? cur.right; + // 删除节点 cur + if (cur != root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + TreeNode? tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + Remove(tmp.val!.Value); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } + } + ``` + +=== "Go" + + ```go title="binary_search_tree.go" + /* 删除节点 */ + func (bst *binarySearchTree) remove(num int) { + cur := bst.root + // 若树为空,直接提前返回 + if cur == nil { + return + } + // 待删除节点之前的节点位置 + var pre *TreeNode = nil + // 循环查找,越过叶节点后跳出 + for cur != nil { + if cur.Val == num { + break + } + pre = cur + if cur.Val.(int) < num { + // 待删除节点在右子树中 + cur = cur.Right + } else { + // 待删除节点在左子树中 + cur = cur.Left + } + } + // 若无待删除节点,则直接返回 + if cur == nil { + return + } + // 子节点数为 0 或 1 + if cur.Left == nil || cur.Right == nil { + var child *TreeNode = nil + // 取出待删除节点的子节点 + if cur.Left != nil { + child = cur.Left + } else { + child = cur.Right + } + // 删除节点 cur + if cur != bst.root { + if pre.Left == cur { + pre.Left = child + } else { + pre.Right = child + } + } else { + // 若删除节点为根节点,则重新指定根节点 + bst.root = child + } + // 子节点数为 2 + } else { + // 获取中序遍历中待删除节点 cur 的下一个节点 + tmp := cur.Right + for tmp.Left != nil { + tmp = tmp.Left + } + // 递归删除节点 tmp + bst.remove(tmp.Val.(int)) + // 用 tmp 覆盖 cur + cur.Val = tmp.Val + } + } + ``` + +=== "Swift" + + ```swift title="binary_search_tree.swift" + /* 删除节点 */ + func remove(num: Int) { + // 若树为空,直接提前返回 + if root == nil { + return + } + var cur = root + var pre: TreeNode? + // 循环查找,越过叶节点后跳出 + while cur != nil { + // 找到待删除节点,跳出循环 + if cur!.val == num { + break + } + pre = cur + // 待删除节点在 cur 的右子树中 + if cur!.val < num { + cur = cur?.right + } + // 待删除节点在 cur 的左子树中 + else { + cur = cur?.left + } + } + // 若无待删除节点,则直接返回 + if cur == nil { + return + } + // 子节点数量 = 0 or 1 + if cur?.left == nil || cur?.right == nil { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + let child = cur?.left ?? cur?.right + // 删除节点 cur + if cur !== root { + if pre?.left === cur { + pre?.left = child + } else { + pre?.right = child + } + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur?.right + while tmp?.left != nil { + tmp = tmp?.left + } + // 递归删除节点 tmp + remove(num: tmp!.val) + // 用 tmp 覆盖 cur + cur?.val = tmp!.val + } + } + ``` + +=== "JS" + + ```javascript title="binary_search_tree.js" + /* 删除节点 */ + remove(num) { + // 若树为空,直接提前返回 + if (this.root === null) return; + let cur = this.root, + pre = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到待删除节点,跳出循环 + if (cur.val === num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 待删除节点在 cur 的左子树中 + else cur = cur.left; + } + // 若无待删除节点,则直接返回 + if (cur === null) return; + // 子节点数量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + const child = cur.left !== null ? cur.left : cur.right; + // 删除节点 cur + if (cur !== this.root) { + if (pre.left === cur) pre.left = child; + else pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + this.root = child; + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + let tmp = cur.right; + while (tmp.left !== null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + this.remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } + } + ``` + +=== "TS" + + ```typescript title="binary_search_tree.ts" + /* 删除节点 */ + remove(num: number): void { + // 若树为空,直接提前返回 + if (this.root === null) return; + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到待删除节点,跳出循环 + if (cur.val === num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 待删除节点在 cur 的左子树中 + else cur = cur.left; + } + // 若无待删除节点,则直接返回 + if (cur === null) return; + // 子节点数量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + const child: TreeNode | null = + cur.left !== null ? cur.left : cur.right; + // 删除节点 cur + if (cur !== this.root) { + if (pre!.left === cur) pre!.left = child; + else pre!.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + this.root = child; + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + let tmp: TreeNode | null = cur.right; + while (tmp!.left !== null) { + tmp = tmp!.left; + } + // 递归删除节点 tmp + this.remove(tmp!.val); + // 用 tmp 覆盖 cur + cur.val = tmp!.val; + } + } + ``` + +=== "Dart" + + ```dart title="binary_search_tree.dart" + /* 删除节点 */ + void remove(int _num) { + // 若树为空,直接提前返回 + if (_root == null) return; + TreeNode? cur = _root; + TreeNode? pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.val == _num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < _num) + cur = cur.right; + // 待删除节点在 cur 的左子树中 + else + cur = cur.left; + } + // 若无待删除节点,直接返回 + if (cur == null) return; + // 子节点数量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + TreeNode? child = cur.left ?? cur.right; + // 删除节点 cur + if (cur != _root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + _root = child; + } + } else { + // 子节点数量 = 2 + // 获取中序遍历中 cur 的下一个节点 + TreeNode? tmp = cur.right; + while (tmp!.left != null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } + } + ``` + +=== "Rust" + + ```rust title="binary_search_tree.rs" + /* 删除节点 */ + pub fn remove(&mut self, num: i32) { + // 若树为空,直接提前返回 + if self.root.is_none() { + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 循环查找,越过叶节点后跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到待删除节点,跳出循环 + Ordering::Equal => break, + // 待删除节点在 cur 的右子树中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 待删除节点在 cur 的左子树中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 若无待删除节点,则直接返回 + if cur.is_none() { + return; + } + let cur = cur.unwrap(); + let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); + match (left_child.clone(), right_child.clone()) { + // 子节点数量 = 0 or 1 + (None, None) | (Some(_), None) | (None, Some(_)) => { + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + let child = left_child.or(right_child); + let pre = pre.unwrap(); + // 删除节点 cur + if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { + let left = pre.borrow().left.clone(); + if left.is_some() && Rc::ptr_eq(&left.as_ref().unwrap(), &cur) { + pre.borrow_mut().left = child; + } else { + pre.borrow_mut().right = child; + } + } else { + // 若删除节点为根节点,则重新指定根节点 + self.root = child; + } + } + // 子节点数量 = 2 + (Some(_), Some(_)) => { + // 获取中序遍历中 cur 的下一个节点 + let mut tmp = cur.borrow().right.clone(); + while let Some(node) = tmp.clone() { + if node.borrow().left.is_some() { + tmp = node.borrow().left.clone(); + } else { + break; + } + } + let tmpval = tmp.unwrap().borrow().val; + // 递归删除节点 tmp + self.remove(tmpval); + // 用 tmp 覆盖 cur + cur.borrow_mut().val = tmpval; + } + } + } + ``` + +=== "C" + + ```c title="binary_search_tree.c" + /* 删除节点 */ + // 由于引入了 stdio.h ,此处无法使用 remove 关键词 + void removeItem(BinarySearchTree *bst, int num) { + // 若树为空,直接提前返回 + if (bst->root == NULL) + return; + TreeNode *cur = bst->root, *pre = NULL; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + // 找到待删除节点,跳出循环 + if (cur->val == num) + break; + pre = cur; + if (cur->val < num) { + // 待删除节点在 root 的右子树中 + cur = cur->right; + } else { + // 待删除节点在 root 的左子树中 + cur = cur->left; + } + } + // 若无待删除节点,则直接返回 + if (cur == NULL) + return; + // 判断待删除节点是否存在子节点 + if (cur->left == NULL || cur->right == NULL) { + /* 子节点数量 = 0 or 1 */ + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + TreeNode *child = cur->left != NULL ? cur->left : cur->right; + // 删除节点 cur + if (pre->left == cur) { + pre->left = child; + } else { + pre->right = child; + } + // 释放内存 + free(cur); + } else { + /* 子节点数量 = 2 */ + // 获取中序遍历中 cur 的下一个节点 + TreeNode *tmp = cur->right; + while (tmp->left != NULL) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 递归删除节点 tmp + removeItem(bst, tmp->val); + // 用 tmp 覆盖 cur + cur->val = tmpVal; + } + } + ``` + +=== "Kotlin" + + ```kotlin title="binary_search_tree.kt" + /* 删除节点 */ + fun remove(num: Int) { + // 若树为空,直接提前返回 + if (root == null) return + var cur = root + var pre: TreeNode? = null + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.value == num) break + pre = cur + // 待删除节点在 cur 的右子树中 + cur = if (cur.value < num) cur.right + // 待删除节点在 cur 的左子树中 + else cur.left + } + // 若无待删除节点,则直接返回 + if (cur == null) return + // 子节点数量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + val child = if (cur.left != null) cur.left else cur.right + // 删除节点 cur + if (cur != root) { + if (pre!!.left == cur) pre.left = child + else pre.right = child + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child + } + // 子节点数量 = 2 + } else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur.right + while (tmp!!.left != null) { + tmp = tmp.left + } + // 递归删除节点 tmp + remove(tmp.value) + // 用 tmp 覆盖 cur + cur.value = tmp.value + } + } + ``` + +=== "Ruby" + + ```ruby title="binary_search_tree.rb" + [class]{BinarySearchTree}-[func]{remove} + ``` + +=== "Zig" + + ```zig title="binary_search_tree.zig" + // 删除节点 + fn remove(self: *Self, num: T) void { + // 若树为空,直接提前返回 + if (self.root == null) return; + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.?.val == num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.?.val < num) { + cur = cur.?.right; + // 待删除节点在 cur 的左子树中 + } else { + cur = cur.?.left; + } + } + // 若无待删除节点,则直接返回 + if (cur == null) return; + // 子节点数量 = 0 or 1 + if (cur.?.left == null or cur.?.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + var child = if (cur.?.left != null) cur.?.left else cur.?.right; + // 删除节点 cur + if (pre.?.left == cur) { + pre.?.left = child; + } else { + pre.?.right = child; + } + // 子节点数量 = 2 + } else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur.?.right; + while (tmp.?.left != null) { + tmp = tmp.?.left; + } + var tmp_val = tmp.?.val; + // 递归删除节点 tmp + self.remove(tmp.?.val); + // 用 tmp 覆盖 cur + cur.?.val = tmp_val; + } + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +### 4.   In-order traversal is ordered + +As shown in the Figure 7-22 , the in-order traversal of a binary tree follows the "left $\rightarrow$ root $\rightarrow$ right" traversal order, and a binary search tree satisfies the size relationship "left child node < root node < right child node". + +This means that in-order traversal in a binary search tree always traverses the next smallest node first, thus deriving an important property: **The in-order traversal sequence of a binary search tree is ascending**. + +Using the ascending property of in-order traversal, obtaining ordered data in a binary search tree requires only $O(n)$ time, without the need for additional sorting operations, which is very efficient. + +![In-order traversal sequence of a binary search tree](binary_search_tree.assets/bst_inorder_traversal.png){ class="animation-figure" } + +

Figure 7-22   In-order traversal sequence of a binary search tree

+ +## 7.4.2   Efficiency of binary search trees + +Given a set of data, we consider using an array or a binary search tree for storage. Observing the Table 7-2 , the operations on a binary search tree all have logarithmic time complexity, which is stable and efficient. Only in scenarios of high-frequency addition and low-frequency search and removal, arrays are more efficient than binary search trees. + +

Table 7-2   Efficiency comparison between arrays and search trees

+ +
+ +| | Unsorted array | Binary search tree | +| -------------- | -------------- | ------------------ | +| Search element | $O(n)$ | $O(\log n)$ | +| Insert element | $O(1)$ | $O(\log n)$ | +| Remove element | $O(n)$ | $O(\log n)$ | + +
+ +In ideal conditions, the binary search tree is "balanced," thus any node can be found within $\log n$ loops. + +However, continuously inserting and removing nodes in a binary search tree may lead to the binary tree degenerating into a chain list as shown in the Figure 7-23 , at which point the time complexity of various operations also degrades to $O(n)$. + +![Degradation of a binary search tree](binary_search_tree.assets/bst_degradation.png){ class="animation-figure" } + +

Figure 7-23   Degradation of a binary search tree

+ +## 7.4.3   Common applications of binary search trees + +- Used as multi-level indexes in systems to implement efficient search, insertion, and removal operations. +- Serves as the underlying data structure for certain search algorithms. +- Used to store data streams to maintain their ordered state. diff --git a/en/docs/chapter_tree/binary_tree.md b/en/docs/chapter_tree/binary_tree.md new file mode 100644 index 000000000..02abb2d45 --- /dev/null +++ b/en/docs/chapter_tree/binary_tree.md @@ -0,0 +1,686 @@ +--- +comments: true +--- + +# 7.1   Binary tree + +A "binary tree" 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. + +=== "Python" + + ```python title="" + class TreeNode: + """Binary tree node""" + def __init__(self, val: int): + self.val: int = val # Node value + self.left: TreeNode | None = None # Reference to left child node + self.right: TreeNode | None = None # Reference to right child node + ``` + +=== "C++" + + ```cpp title="" + /* Binary tree node */ + struct TreeNode { + int val; // Node value + TreeNode *left; // Pointer to left child node + TreeNode *right; // Pointer to right child node + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + }; + ``` + +=== "Java" + + ```java title="" + /* Binary tree node */ + class TreeNode { + int val; // Node value + TreeNode left; // Reference to left child node + TreeNode right; // Reference to right child node + TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* Binary tree node */ + class TreeNode(int? x) { + public int? val = x; // Node value + public TreeNode? left; // Reference to left child node + public TreeNode? right; // Reference to right child node + } + ``` + +=== "Go" + + ```go title="" + /* Binary tree node */ + type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode + } + /* 构造方法 */ + func NewTreeNode(v int) *TreeNode { + return &TreeNode{ + Left: nil, // Pointer to left child node + Right: nil, // Pointer to right child node + Val: v, // Node value + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Binary tree node */ + class TreeNode { + var val: Int // Node value + var left: TreeNode? // Reference to left child node + var right: TreeNode? // Reference to right child node + + init(x: Int) { + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Binary tree node */ + class TreeNode { + val; // Node value + left; // Pointer to left child node + right; // Pointer to right child node + constructor(val, left, right) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Binary tree node */ + class TreeNode { + val: number; + left: TreeNode | null; + right: TreeNode | null; + + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; // Node value + this.left = left === undefined ? null : left; // Reference to left child node + this.right = right === undefined ? null : right; // Reference to right child node + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Binary tree node */ + class TreeNode { + int val; // Node value + TreeNode? left; // Reference to left child node + TreeNode? right; // Reference to right child node + TreeNode(this.val, [this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Binary tree node */ + struct TreeNode { + val: i32, // Node value + left: Option>>, // Reference to left child node + right: Option>>, // Reference to right child node + } + + impl TreeNode { + /* 构造方法 */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* Binary tree node */ + typedef struct TreeNode { + int val; // Node value + int height; // 节点高度 + struct TreeNode *left; // Pointer to left child node + struct TreeNode *right; // Pointer to right child node + } TreeNode; + + /* 构造函数 */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Binary tree node */ + class TreeNode(val _val: Int) { // Node value + val left: TreeNode? = null // Reference to left child node + val right: TreeNode? = null // Reference to right child node + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +Each node has two references (pointers), pointing to the "left-child node" and "right-child node," respectively. This node is called the "parent node" of these two child nodes. When given a node of a binary tree, we call the tree formed by this node's left child and all nodes under it the "left subtree" of this node. Similarly, the "right subtree" can be defined. + +**In a binary tree, except for leaf nodes, all other nodes contain child nodes and non-empty subtrees.** As shown in the Figure 7-1 , if "Node 2" is considered as the parent node, then its left and right child nodes are "Node 4" and "Node 5," respectively. The left subtree is "the tree formed by Node 4 and all nodes under it," and the right subtree is "the tree formed by Node 5 and all nodes under it." + +![Parent Node, child Node, subtree](binary_tree.assets/binary_tree_definition.png){ class="animation-figure" } + +

Figure 7-1   Parent Node, child Node, subtree

+ +## 7.1.1   Common terminology of binary trees + +The commonly used terminology of binary trees is shown in the following figure. + +- "Root node": The node at the top level of the binary tree, which has no parent node. +- "Leaf node": A node with no children, both of its pointers point to `None`. +- "Edge": The line segment connecting two nodes, i.e., node reference (pointer). +- The "level" of a node: Incrementing from top to bottom, with the root node's level being 1. +- The "degree" of a node: The number of a node's children. In a binary tree, the degree can be 0, 1, or 2. +- The "height" of a binary tree: The number of edges passed from the root node to the farthest leaf node. +- The "depth" of a node: The number of edges passed from the root node to the node. +- The "height" of a node: The number of edges from the farthest leaf node to the node. + +![Common Terminology of Binary Trees](binary_tree.assets/binary_tree_terminology.png){ class="animation-figure" } + +

Figure 7-2   Common Terminology of Binary Trees

+ +!!! 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. + +## 7.1.2   Basic operations of binary trees + +### 1.   Initializing a binary tree + +Similar to a linked list, initialize nodes first, then construct references (pointers). + +=== "Python" + + ```python title="binary_tree.py" + # Initializing a binary tree + # Initializing nodes + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # Linking references (pointers) between nodes + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode* n1 = new TreeNode(1); + TreeNode* n2 = new TreeNode(2); + TreeNode* n3 = new TreeNode(3); + TreeNode* n4 = new TreeNode(4); + TreeNode* n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Java" + + ```java title="binary_tree.java" + // Initializing nodes + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* Initializing a binary tree */ + // Initializing nodes + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // Linking references (pointers) between nodes + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + // Initializing nodes + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // Linking references (pointers) between nodes + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* Initializing a binary tree */ + // Initializing nodes + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* Initializing a binary tree */ + // Initializing nodes + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + // Initializing nodes + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // Linking references (pointers) between nodes + n1.borrow_mut().left = Some(n2.clone()); + n1.borrow_mut().right = Some(n3); + n2.borrow_mut().left = Some(n4); + n2.borrow_mut().right = Some(n5); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // Linking references (pointers) between nodes + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + // Initializing nodes + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // Linking references (pointers) between nodes + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "Code visualization" + + 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&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 2.   Inserting and removing nodes + +Similar to a linked list, inserting and removing nodes in a binary tree can be achieved by modifying pointers. The Figure 7-3 provides an example. + +![Inserting and removing nodes in a binary tree](binary_tree.assets/binary_tree_add_remove.png){ class="animation-figure" } + +

Figure 7-3   Inserting and removing nodes in a binary tree

+ +=== "Python" + + ```python title="binary_tree.py" + # Inserting and removing nodes + p = TreeNode(0) + # Inserting node P between n1 -> n2 + n1.left = p + p.left = n2 + # Removing node P + n1.left = n2 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* Inserting and removing nodes */ + TreeNode* P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1->left = P; + P->left = n2; + // Removing node P + n1->left = n2; + ``` + +=== "Java" + + ```java title="binary_tree.java" + TreeNode P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* Inserting and removing nodes */ + TreeNode P = new(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* Inserting and removing nodes */ + // Inserting node P between n1 and n2 + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + // Removing node P + n1.Left = n2 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + let P = TreeNode(x: 0) + // Inserting node P between n1 and n2 + n1.left = P + P.left = n2 + // Removing node P + n1.left = n2 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* Inserting and removing nodes */ + let P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* Inserting and removing nodes */ + const P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* Inserting and removing nodes */ + TreeNode P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + let p = TreeNode::new(0); + // Inserting node P between n1 and n2 + n1.borrow_mut().left = Some(p.clone()); + p.borrow_mut().left = Some(n2.clone()); + // Removing node P + n1.borrow_mut().left = Some(n2); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* Inserting and removing nodes */ + TreeNode *P = newTreeNode(0); + // Inserting node P between n1 and n2 + n1->left = P; + P->left = n2; + // Removing node P + n1->left = n2; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + val P = TreeNode(0) + // Inserting node P between n1 and n2 + n1.left = P + P.left = n2 + // Removing node P + n1.left = n2 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "Code visualization" + + 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 + + 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. + +## 7.1.3   Common types of binary trees + +### 1.   Perfect binary tree + +As shown in the Figure 7-4 , in a "perfect binary tree," 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. + +!!! tip + + Please note that in the Chinese community, a perfect binary tree is often referred to as a "full binary tree." + +![Perfect binary tree](binary_tree.assets/perfect_binary_tree.png){ class="animation-figure" } + +

Figure 7-4   Perfect binary tree

+ +### 2.   Complete binary tree + +As shown in the Figure 7-5 , a "complete binary tree" has only the bottom level nodes not fully filled, and the bottom level nodes are filled as far left as possible. + +![Complete binary tree](binary_tree.assets/complete_binary_tree.png){ class="animation-figure" } + +

Figure 7-5   Complete binary tree

+ +### 3.   Full binary tree + +As shown in the Figure 7-6 , a "full binary tree" has all nodes except leaf nodes having two children. + +![Full binary tree](binary_tree.assets/full_binary_tree.png){ class="animation-figure" } + +

Figure 7-6   Full binary tree

+ +### 4.   Balanced binary tree + +As shown in the Figure 7-7 , in a "balanced binary tree," the absolute difference in height between the left and right subtrees of any node does not exceed 1. + +![Balanced binary tree](binary_tree.assets/balanced_binary_tree.png){ class="animation-figure" } + +

Figure 7-7   Balanced binary tree

+ +## 7.1.4   Degeneration of binary trees + +The 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". + +- 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)$. + +![The Best and Worst Structures of Binary Trees](binary_tree.assets/binary_tree_best_worst_cases.png){ class="animation-figure" } + +

Figure 7-8   The Best and Worst Structures of Binary Trees

+ +As shown in the Table 7-1 , in the best and worst structures, the number of leaf nodes, total number of nodes, and height of the binary tree reach their maximum or minimum values. + +

Table 7-1   The Best and Worst Structures of Binary Trees

+ +
+ +| | Perfect binary tree | Linked list | +| ----------------------------------------------- | ------------------- | ----------- | +| Number of nodes at level $i$ | $2^{i-1}$ | $1$ | +| Number of leaf nodes in a tree with height $h$ | $2^h$ | $1$ | +| Total number of nodes in a tree with height $h$ | $2^{h+1} - 1$ | $h + 1$ | +| Height of a tree with $n$ total nodes | $\log_2 (n+1) - 1$ | $n - 1$ | + +
diff --git a/en/docs/chapter_tree/binary_tree_traversal.md b/en/docs/chapter_tree/binary_tree_traversal.md new file mode 100755 index 000000000..011002271 --- /dev/null +++ b/en/docs/chapter_tree/binary_tree_traversal.md @@ -0,0 +1,883 @@ +--- +comments: true +--- + +# 7.2   Binary tree traversal + +From the perspective of physical structure, a tree is a data structure based on linked lists, hence its traversal method involves accessing nodes one by one through pointers. However, a tree is a non-linear data structure, which makes traversing a tree more complex than traversing a linked list, requiring the assistance of search algorithms to achieve. + +Common traversal methods for binary trees include level-order traversal, preorder traversal, inorder traversal, and postorder traversal, among others. + +## 7.2.1   Level-order traversal + +As shown in the Figure 7-9 , "level-order traversal" traverses the binary tree from top to bottom, layer by layer, and accesses nodes in each layer in a left-to-right order. + +Level-order traversal essentially belongs to "breadth-first traversal", also known as "breadth-first search (BFS)", which embodies a "circumferentially outward expanding" layer-by-layer traversal method. + +![Level-order traversal of a binary tree](binary_tree_traversal.assets/binary_tree_bfs.png){ class="animation-figure" } + +

Figure 7-9   Level-order traversal of a binary tree

+ +### 1.   Code implementation + +Breadth-first traversal is usually implemented with the help of a "queue". The queue follows the "first in, first out" rule, while breadth-first traversal follows the "layer-by-layer progression" rule, the underlying ideas of the two are consistent. The implementation code is as follows: + +=== "Python" + + ```python title="binary_tree_bfs.py" + def level_order(root: TreeNode | None) -> list[int]: + """层序遍历""" + # 初始化队列,加入根节点 + queue: deque[TreeNode] = deque() + queue.append(root) + # 初始化一个列表,用于保存遍历序列 + res = [] + while queue: + node: TreeNode = queue.popleft() # 队列出队 + res.append(node.val) # 保存节点值 + if node.left is not None: + queue.append(node.left) # 左子节点入队 + if node.right is not None: + queue.append(node.right) # 右子节点入队 + return res + ``` + +=== "C++" + + ```cpp title="binary_tree_bfs.cpp" + /* 层序遍历 */ + vector levelOrder(TreeNode *root) { + // 初始化队列,加入根节点 + queue queue; + queue.push(root); + // 初始化一个列表,用于保存遍历序列 + vector vec; + while (!queue.empty()) { + TreeNode *node = queue.front(); + queue.pop(); // 队列出队 + vec.push_back(node->val); // 保存节点值 + if (node->left != nullptr) + queue.push(node->left); // 左子节点入队 + if (node->right != nullptr) + queue.push(node->right); // 右子节点入队 + } + return vec; + } + ``` + +=== "Java" + + ```java title="binary_tree_bfs.java" + /* 层序遍历 */ + List levelOrder(TreeNode root) { + // 初始化队列,加入根节点 + Queue queue = new LinkedList<>(); + queue.add(root); + // 初始化一个列表,用于保存遍历序列 + List list = new ArrayList<>(); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); // 队列出队 + list.add(node.val); // 保存节点值 + if (node.left != null) + queue.offer(node.left); // 左子节点入队 + if (node.right != null) + queue.offer(node.right); // 右子节点入队 + } + return list; + } + ``` + +=== "C#" + + ```csharp title="binary_tree_bfs.cs" + /* 层序遍历 */ + List LevelOrder(TreeNode root) { + // 初始化队列,加入根节点 + Queue queue = new(); + queue.Enqueue(root); + // 初始化一个列表,用于保存遍历序列 + List list = []; + while (queue.Count != 0) { + TreeNode node = queue.Dequeue(); // 队列出队 + list.Add(node.val!.Value); // 保存节点值 + if (node.left != null) + queue.Enqueue(node.left); // 左子节点入队 + if (node.right != null) + queue.Enqueue(node.right); // 右子节点入队 + } + return list; + } + ``` + +=== "Go" + + ```go title="binary_tree_bfs.go" + /* 层序遍历 */ + func levelOrder(root *TreeNode) []any { + // 初始化队列,加入根节点 + queue := list.New() + queue.PushBack(root) + // 初始化一个切片,用于保存遍历序列 + nums := make([]any, 0) + for queue.Len() > 0 { + // 队列出队 + node := queue.Remove(queue.Front()).(*TreeNode) + // 保存节点值 + nums = append(nums, node.Val) + if node.Left != nil { + // 左子节点入队 + queue.PushBack(node.Left) + } + if node.Right != nil { + // 右子节点入队 + queue.PushBack(node.Right) + } + } + return nums + } + ``` + +=== "Swift" + + ```swift title="binary_tree_bfs.swift" + /* 层序遍历 */ + func levelOrder(root: TreeNode) -> [Int] { + // 初始化队列,加入根节点 + var queue: [TreeNode] = [root] + // 初始化一个列表,用于保存遍历序列 + var list: [Int] = [] + while !queue.isEmpty { + let node = queue.removeFirst() // 队列出队 + list.append(node.val) // 保存节点值 + if let left = node.left { + queue.append(left) // 左子节点入队 + } + if let right = node.right { + queue.append(right) // 右子节点入队 + } + } + return list + } + ``` + +=== "JS" + + ```javascript title="binary_tree_bfs.js" + /* 层序遍历 */ + function levelOrder(root) { + // 初始化队列,加入根节点 + const queue = [root]; + // 初始化一个列表,用于保存遍历序列 + const list = []; + while (queue.length) { + let node = queue.shift(); // 队列出队 + list.push(node.val); // 保存节点值 + if (node.left) queue.push(node.left); // 左子节点入队 + if (node.right) queue.push(node.right); // 右子节点入队 + } + return list; + } + ``` + +=== "TS" + + ```typescript title="binary_tree_bfs.ts" + /* 层序遍历 */ + function levelOrder(root: TreeNode | null): number[] { + // 初始化队列,加入根节点 + const queue = [root]; + // 初始化一个列表,用于保存遍历序列 + const list: number[] = []; + while (queue.length) { + let node = queue.shift() as TreeNode; // 队列出队 + list.push(node.val); // 保存节点值 + if (node.left) { + queue.push(node.left); // 左子节点入队 + } + if (node.right) { + queue.push(node.right); // 右子节点入队 + } + } + return list; + } + ``` + +=== "Dart" + + ```dart title="binary_tree_bfs.dart" + /* 层序遍历 */ + List levelOrder(TreeNode? root) { + // 初始化队列,加入根节点 + Queue queue = Queue(); + queue.add(root); + // 初始化一个列表,用于保存遍历序列 + List res = []; + while (queue.isNotEmpty) { + TreeNode? node = queue.removeFirst(); // 队列出队 + res.add(node!.val); // 保存节点值 + if (node.left != null) queue.add(node.left); // 左子节点入队 + if (node.right != null) queue.add(node.right); // 右子节点入队 + } + return res; + } + ``` + +=== "Rust" + + ```rust title="binary_tree_bfs.rs" + /* 层序遍历 */ + fn level_order(root: &Rc>) -> Vec { + // 初始化队列,加入根节点 + let mut que = VecDeque::new(); + que.push_back(Rc::clone(&root)); + // 初始化一个列表,用于保存遍历序列 + let mut vec = Vec::new(); + + while let Some(node) = que.pop_front() { + // 队列出队 + vec.push(node.borrow().val); // 保存节点值 + if let Some(left) = node.borrow().left.as_ref() { + que.push_back(Rc::clone(left)); // 左子节点入队 + } + if let Some(right) = node.borrow().right.as_ref() { + que.push_back(Rc::clone(right)); // 右子节点入队 + }; + } + vec + } + ``` + +=== "C" + + ```c title="binary_tree_bfs.c" + /* 层序遍历 */ + int *levelOrder(TreeNode *root, int *size) { + /* 辅助队列 */ + int front, rear; + int index, *arr; + TreeNode *node; + TreeNode **queue; + + /* 辅助队列 */ + queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); + // 队列指针 + front = 0, rear = 0; + // 加入根节点 + queue[rear++] = root; + // 初始化一个列表,用于保存遍历序列 + /* 辅助数组 */ + arr = (int *)malloc(sizeof(int) * MAX_SIZE); + // 数组指针 + index = 0; + while (front < rear) { + // 队列出队 + node = queue[front++]; + // 保存节点值 + arr[index++] = node->val; + if (node->left != NULL) { + // 左子节点入队 + queue[rear++] = node->left; + } + if (node->right != NULL) { + // 右子节点入队 + queue[rear++] = node->right; + } + } + // 更新数组长度的值 + *size = index; + arr = realloc(arr, sizeof(int) * (*size)); + + // 释放辅助数组空间 + free(queue); + return arr; + } + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree_bfs.kt" + /* 层序遍历 */ + fun levelOrder(root: TreeNode?): MutableList { + // 初始化队列,加入根节点 + val queue = LinkedList() + queue.add(root) + // 初始化一个列表,用于保存遍历序列 + val list = ArrayList() + while (!queue.isEmpty()) { + val node = queue.poll() // 队列出队 + list.add(node?.value!!) // 保存节点值 + if (node.left != null) queue.offer(node.left) // 左子节点入队 + + if (node.right != null) queue.offer(node.right) // 右子节点入队 + } + return list + } + ``` + +=== "Ruby" + + ```ruby title="binary_tree_bfs.rb" + [class]{}-[func]{level_order} + ``` + +=== "Zig" + + ```zig title="binary_tree_bfs.zig" + // 层序遍历 + fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { + // 初始化队列,加入根节点 + const L = std.TailQueue(*inc.TreeNode(T)); + var queue = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + queue.append(root_node); + // 初始化一个列表,用于保存遍历序列 + var list = std.ArrayList(T).init(std.heap.page_allocator); + while (queue.len > 0) { + var queue_node = queue.popFirst().?; // 队列出队 + var node = queue_node.data; + try list.append(node.val); // 保存节点值 + if (node.left != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + queue.append(tmp_node); // 左子节点入队 + } + if (node.right != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + queue.append(tmp_node); // 右子节点入队 + } + } + return list; + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +### 2.   Complexity analysis + +- **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time, where $n$ is the number of nodes. +- **Space complexity is $O(n)$**: In the worst case, i.e., a full binary tree, before traversing to the lowest level, the queue can contain at most $(n + 1) / 2$ nodes at the same time, occupying $O(n)$ space. + +## 7.2.2   Preorder, inorder, and postorder traversal + +Correspondingly, preorder, inorder, and postorder traversal all belong to "depth-first traversal", also known as "depth-first search (DFS)", which embodies a "proceed to the end first, then backtrack and continue" traversal method. + +The Figure 7-10 shows the working principle of performing a depth-first traversal on a binary tree. **Depth-first traversal is like walking around the perimeter of the entire binary tree**, encountering three positions at each node, corresponding to preorder traversal, inorder traversal, and postorder traversal. + +![Preorder, inorder, and postorder traversal of a binary search tree](binary_tree_traversal.assets/binary_tree_dfs.png){ class="animation-figure" } + +

Figure 7-10   Preorder, inorder, and postorder traversal of a binary search tree

+ +### 1.   Code implementation + +Depth-first search is usually implemented based on recursion: + +=== "Python" + + ```python title="binary_tree_dfs.py" + def pre_order(root: TreeNode | None): + """前序遍历""" + if root is None: + return + # 访问优先级:根节点 -> 左子树 -> 右子树 + res.append(root.val) + pre_order(root=root.left) + pre_order(root=root.right) + + def in_order(root: TreeNode | None): + """中序遍历""" + if root is None: + return + # 访问优先级:左子树 -> 根节点 -> 右子树 + in_order(root=root.left) + res.append(root.val) + in_order(root=root.right) + + def post_order(root: TreeNode | None): + """后序遍历""" + if root is None: + return + # 访问优先级:左子树 -> 右子树 -> 根节点 + post_order(root=root.left) + post_order(root=root.right) + res.append(root.val) + ``` + +=== "C++" + + ```cpp title="binary_tree_dfs.cpp" + /* 前序遍历 */ + void preOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + vec.push_back(root->val); + preOrder(root->left); + preOrder(root->right); + } + + /* 中序遍历 */ + void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root->left); + vec.push_back(root->val); + inOrder(root->right); + } + + /* 后序遍历 */ + void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root->left); + postOrder(root->right); + vec.push_back(root->val); + } + ``` + +=== "Java" + + ```java title="binary_tree_dfs.java" + /* 前序遍历 */ + void preOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.add(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* 中序遍历 */ + void inOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root.left); + list.add(root.val); + inOrder(root.right); + } + + /* 后序遍历 */ + void postOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root.left); + postOrder(root.right); + list.add(root.val); + } + ``` + +=== "C#" + + ```csharp title="binary_tree_dfs.cs" + /* 前序遍历 */ + void PreOrder(TreeNode? root) { + if (root == null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.Add(root.val!.Value); + PreOrder(root.left); + PreOrder(root.right); + } + + /* 中序遍历 */ + void InOrder(TreeNode? root) { + if (root == null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + InOrder(root.left); + list.Add(root.val!.Value); + InOrder(root.right); + } + + /* 后序遍历 */ + void PostOrder(TreeNode? root) { + if (root == null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + PostOrder(root.left); + PostOrder(root.right); + list.Add(root.val!.Value); + } + ``` + +=== "Go" + + ```go title="binary_tree_dfs.go" + /* 前序遍历 */ + func preOrder(node *TreeNode) { + if node == nil { + return + } + // 访问优先级:根节点 -> 左子树 -> 右子树 + nums = append(nums, node.Val) + preOrder(node.Left) + preOrder(node.Right) + } + + /* 中序遍历 */ + func inOrder(node *TreeNode) { + if node == nil { + return + } + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(node.Left) + nums = append(nums, node.Val) + inOrder(node.Right) + } + + /* 后序遍历 */ + func postOrder(node *TreeNode) { + if node == nil { + return + } + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(node.Left) + postOrder(node.Right) + nums = append(nums, node.Val) + } + ``` + +=== "Swift" + + ```swift title="binary_tree_dfs.swift" + /* 前序遍历 */ + func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.append(root.val) + preOrder(root: root.left) + preOrder(root: root.right) + } + + /* 中序遍历 */ + func inOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root: root.left) + list.append(root.val) + inOrder(root: root.right) + } + + /* 后序遍历 */ + func postOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root: root.left) + postOrder(root: root.right) + list.append(root.val) + } + ``` + +=== "JS" + + ```javascript title="binary_tree_dfs.js" + /* 前序遍历 */ + function preOrder(root) { + if (root === null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* 中序遍历 */ + function inOrder(root) { + if (root === null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); + } + + /* 后序遍历 */ + function postOrder(root) { + if (root === null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); + } + ``` + +=== "TS" + + ```typescript title="binary_tree_dfs.ts" + /* 前序遍历 */ + function preOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* 中序遍历 */ + function inOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); + } + + /* 后序遍历 */ + function postOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); + } + ``` + +=== "Dart" + + ```dart title="binary_tree_dfs.dart" + /* 前序遍历 */ + void preOrder(TreeNode? node) { + if (node == null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.add(node.val); + preOrder(node.left); + preOrder(node.right); + } + + /* 中序遍历 */ + void inOrder(TreeNode? node) { + if (node == null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(node.left); + list.add(node.val); + inOrder(node.right); + } + + /* 后序遍历 */ + void postOrder(TreeNode? node) { + if (node == null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(node.left); + postOrder(node.right); + list.add(node.val); + } + ``` + +=== "Rust" + + ```rust title="binary_tree_dfs.rs" + /* 前序遍历 */ + fn pre_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + if let Some(node) = root { + // 访问优先级:根节点 -> 左子树 -> 右子树 + result.push(node.borrow().val); + result.append(&mut pre_order(node.borrow().left.as_ref())); + result.append(&mut pre_order(node.borrow().right.as_ref())); + } + result + } + + /* 中序遍历 */ + fn in_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + if let Some(node) = root { + // 访问优先级:左子树 -> 根节点 -> 右子树 + result.append(&mut in_order(node.borrow().left.as_ref())); + result.push(node.borrow().val); + result.append(&mut in_order(node.borrow().right.as_ref())); + } + result + } + + /* 后序遍历 */ + fn post_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + if let Some(node) = root { + // 访问优先级:左子树 -> 右子树 -> 根节点 + result.append(&mut post_order(node.borrow().left.as_ref())); + result.append(&mut post_order(node.borrow().right.as_ref())); + result.push(node.borrow().val); + } + result + } + ``` + +=== "C" + + ```c title="binary_tree_dfs.c" + /* 前序遍历 */ + void preOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + arr[(*size)++] = root->val; + preOrder(root->left, size); + preOrder(root->right, size); + } + + /* 中序遍历 */ + void inOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root->left, size); + arr[(*size)++] = root->val; + inOrder(root->right, size); + } + + /* 后序遍历 */ + void postOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root->left, size); + postOrder(root->right, size); + arr[(*size)++] = root->val; + } + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree_dfs.kt" + /* 前序遍历 */ + fun preOrder(root: TreeNode?) { + if (root == null) return + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.add(root.value) + preOrder(root.left) + preOrder(root.right) + } + + /* 中序遍历 */ + fun inOrder(root: TreeNode?) { + if (root == null) return + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root.left) + list.add(root.value) + inOrder(root.right) + } + + /* 后序遍历 */ + fun postOrder(root: TreeNode?) { + if (root == null) return + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root.left) + postOrder(root.right) + list.add(root.value) + } + ``` + +=== "Ruby" + + ```ruby title="binary_tree_dfs.rb" + [class]{}-[func]{pre_order} + + [class]{}-[func]{in_order} + + [class]{}-[func]{post_order} + ``` + +=== "Zig" + + ```zig title="binary_tree_dfs.zig" + // 前序遍历 + fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + try list.append(root.?.val); + try preOrder(T, root.?.left); + try preOrder(T, root.?.right); + } + + // 中序遍历 + fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + try inOrder(T, root.?.left); + try list.append(root.?.val); + try inOrder(T, root.?.right); + } + + // 后序遍历 + fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + try postOrder(T, root.?.left); + try postOrder(T, root.?.right); + try list.append(root.?.val); + } + ``` + +??? pythontutor "Code Visualization" + +
+ + +!!! tip + + Depth-first search can also be implemented based on iteration, interested readers can study this on their own. + +The Figure 7-11 shows the recursive process of preorder traversal of a binary tree, which can be divided into two opposite parts: "recursion" and "return". + +1. "Recursion" means starting a new method, the program accesses the next node in this process. +2. "Return" means the function returns, indicating the current node has been fully accessed. + +=== "<1>" + ![The recursive process of preorder traversal](binary_tree_traversal.assets/preorder_step1.png){ class="animation-figure" } + +=== "<2>" + ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png){ class="animation-figure" } + +=== "<3>" + ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png){ class="animation-figure" } + +=== "<4>" + ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png){ class="animation-figure" } + +=== "<5>" + ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png){ class="animation-figure" } + +=== "<6>" + ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png){ class="animation-figure" } + +=== "<7>" + ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png){ class="animation-figure" } + +=== "<8>" + ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png){ class="animation-figure" } + +=== "<9>" + ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png){ class="animation-figure" } + +=== "<10>" + ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png){ class="animation-figure" } + +=== "<11>" + ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png){ class="animation-figure" } + +

Figure 7-11   The recursive process of preorder traversal

+ +### 2.   Complexity analysis + +- **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time. +- **Space complexity is $O(n)$**: In the worst case, i.e., the tree degrades into a linked list, the recursion depth reaches $n$, the system occupies $O(n)$ stack frame space. diff --git a/en/docs/chapter_tree/index.md b/en/docs/chapter_tree/index.md new file mode 100644 index 000000000..a5d1da56c --- /dev/null +++ b/en/docs/chapter_tree/index.md @@ -0,0 +1,23 @@ +--- +comments: true +icon: material/graph-outline +--- + +# Chapter 7.   Tree + +![Tree](../assets/covers/chapter_tree.jpg){ class="cover-image" } + +!!! abstract + + The towering tree, full of vitality with its roots deep and leaves lush, branches spreading wide. + + It vividly demonstrates the form of data divide-and-conquer. + +## Chapter Contents + +- [7.1   Binary Tree](https://www.hello-algo.com/en/chapter_tree/binary_tree/) +- [7.2   Binary Tree Traversal](https://www.hello-algo.com/en/chapter_tree/binary_tree_traversal/) +- [7.3   Array Representation of Tree](https://www.hello-algo.com/en/chapter_tree/array_representation_of_tree/) +- [7.4   Binary Search Tree](https://www.hello-algo.com/en/chapter_tree/binary_search_tree/) +- [7.5   AVL Tree *](https://www.hello-algo.com/en/chapter_tree/avl_tree/) +- [7.6   Summary](https://www.hello-algo.com/en/chapter_tree/summary/) diff --git a/en/docs/chapter_tree/summary.md b/en/docs/chapter_tree/summary.md new file mode 100644 index 000000000..a7cd0a41a --- /dev/null +++ b/en/docs/chapter_tree/summary.md @@ -0,0 +1,58 @@ +--- +comments: true +--- + +# 7.6   Summary + +### 1.   Key review + +- A binary tree is a non-linear data structure that reflects the "divide and conquer" logic of splitting one into two. Each binary tree node contains a value and two pointers, which point to its left and right child nodes, respectively. +- For a node in a binary tree, the tree formed by its left (right) child node and all nodes under it is called the node's left (right) subtree. +- Related terminology of binary trees includes root node, leaf node, level, degree, edge, height, and depth, among others. +- The operations of initializing a binary tree, inserting nodes, and removing nodes are similar to those of linked list operations. +- Common types of binary trees include perfect binary trees, complete binary trees, full binary trees, and balanced binary trees. The perfect binary tree represents the ideal state, while the linked list is the worst state after degradation. +- A binary tree can be represented using an array by arranging the node values and empty slots in a level-order traversal sequence and implementing pointers based on the index mapping relationship between parent nodes and child nodes. +- The level-order traversal of a binary tree is a breadth-first search method, which reflects a layer-by-layer traversal manner of "expanding circle by circle." It is usually implemented using a queue. +- Pre-order, in-order, and post-order traversals are all depth-first search methods, reflecting the traversal manner of "going to the end first, then backtracking to continue." They are usually implemented using recursion. +- A binary search tree is an efficient data structure for element searching, with the time complexity of search, insert, and remove operations all being $O(\log n)$. When a binary search tree degrades into a linked list, these time complexities deteriorate to $O(n)$. +- An AVL tree, also known as a balanced binary search tree, ensures that the tree remains balanced after continuous node insertions and removals through rotation operations. +- Rotation operations in an AVL tree include right rotation, left rotation, right-then-left rotation, and left-then-right rotation. After inserting or removing nodes, an AVL tree performs rotation operations from bottom to top to rebalance the tree. + +### 2.   Q & A + +**Q**: For a binary tree with only one node, are both the height of the tree and the depth of the root node $0$? + +Yes, because height and depth are typically defined as "the number of edges passed." + +**Q**: The insertion and removal in a binary tree are generally completed by a set of operations. What does "a set of operations" refer to here? Can it be understood as the release of resources of the child nodes? + +Taking the binary search tree as an example, the operation of removing a node needs to be handled in three different scenarios, each requiring multiple steps of node operations. + +**Q**: Why are there three sequences: pre-order, in-order, and post-order for DFS traversal of a binary tree, and what are their uses? + +Similar to sequential and reverse traversal of arrays, pre-order, in-order, and post-order traversals are three methods of traversing a binary tree, allowing us to obtain a traversal result in a specific order. For example, in a binary search tree, since the node sizes satisfy `left child node value < root node value < right child node value`, we can obtain an ordered node sequence by traversing the tree in the "left → root → right" priority. + +**Q**: In a right rotation operation that deals with the relationship between the imbalance nodes `node`, `child`, `grand_child`, isn't the connection between `node` and its parent node and the original link of `node` lost after the right rotation? + +We need to view this problem from a recursive perspective. The `right_rotate(root)` operation passes the root node of the subtree and eventually returns the root node of the rotated subtree with `return child`. The connection between the subtree's root node and its parent node is established after this function returns, which is outside the scope of the right rotation operation's maintenance. + +**Q**: In C++, functions are divided into `private` and `public` sections. What considerations are there for this? Why are the `height()` function and the `updateHeight()` function placed in `public` and `private`, respectively? + +It depends on the scope of the method's use. If a method is only used within the class, then it is designed to be `private`. For example, it makes no sense for users to call `updateHeight()` on their own, as it is just a step in the insertion or removal operations. However, `height()` is for accessing node height, similar to `vector.size()`, thus it is set to `public` for use. + +**Q**: How do you build a binary search tree from a set of input data? Is the choice of root node very important? + +Yes, the method for building the tree is provided in the `build_tree()` method in the binary search tree code. As for the choice of the root node, we usually sort the input data and then select the middle element as the root node, recursively building the left and right subtrees. This approach maximizes the balance of the tree. + +**Q**: In Java, do you always have to use the `equals()` method for string comparison? + +In Java, for primitive data types, `==` is used to compare whether the values of two variables are equal. For reference types, the working principles of the two symbols are different. + +- `==`: Used to compare whether two variables point to the same object, i.e., whether their positions in memory are the same. +- `equals()`: Used to compare whether the values of two objects are equal. + +Therefore, to compare values, we should use `equals()`. However, strings initialized with `String a = "hi"; String b = "hi";` are stored in the string constant pool and point to the same object, so `a == b` can also be used to compare the contents of two strings. + +**Q**: Before reaching the bottom level, is the number of nodes in the queue $2^h$ in breadth-first traversal? + +Yes, for example, a full binary tree with height $h = 2$ has a total of $n = 7$ nodes, then the bottom level has $4 = 2^h = (n + 1) / 2$ nodes.