diff --git a/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/codes/cpp/chapter_stack_and_queue/array_queue.cpp index 14df4abd2..387ffd142 100644 --- a/codes/cpp/chapter_stack_and_queue/array_queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -64,13 +64,6 @@ public: return nums[front]; } - /* 访问指定索引元素 */ - int get(int index) { - if (index >= size()) - throw out_of_range("索引越界"); - return nums[(front + index) % capacity()]; - } - /* 将数组转化为 Vector 并返回 */ vector toVector() { int siz = size(); @@ -103,11 +96,7 @@ int main() { /* 访问队首元素 */ int peek = queue->peek(); cout << "队首元素 peek = " << peek << endl; - - /* 访问指定索引元素 */ - int num = queue->get(2); - cout << "队列第 3 个元素为 num = " << num << endl; - + /* 元素出队 */ int poll = queue->poll(); cout << "出队元素 poll = " << poll << ",出队后 queue = "; diff --git a/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/codes/cpp/chapter_stack_and_queue/array_stack.cpp index fe5dc3110..517a18a4d 100644 --- a/codes/cpp/chapter_stack_and_queue/array_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -41,13 +41,6 @@ public: return stack.back(); } - /* 访问索引 index 处元素 */ - int get(int index) { - if(index >= size()) - throw out_of_range("索引越界"); - return stack[index]; - } - /* 返回 Vector */ vector toVector() { return stack; @@ -73,10 +66,6 @@ int main() { int top = stack->top(); cout << "栈顶元素 top = " << top << endl; - /* 访问索引 index 处元素 */ - int num = stack->get(3); - cout << "栈索引 3 处的元素为 num = " << num << endl; - /* 元素出栈 */ int pop = stack->pop(); cout << "出栈元素 pop = " << pop << ",出栈后 stack = "; diff --git a/codes/java/chapter_stack_and_queue/array_queue.java b/codes/java/chapter_stack_and_queue/array_queue.java index 6ffb546c6..198c90714 100644 --- a/codes/java/chapter_stack_and_queue/array_queue.java +++ b/codes/java/chapter_stack_and_queue/array_queue.java @@ -63,13 +63,6 @@ class ArrayQueue { return nums[front]; } - /* 访问索引 index 处元素 */ - int get(int index) { - if (index >= size()) - throw new IndexOutOfBoundsException(); - return nums[(front + index) % capacity()]; - } - /* 返回数组 */ public int[] toArray() { int size = size(); diff --git a/codes/java/chapter_stack_and_queue/array_stack.java b/codes/java/chapter_stack_and_queue/array_stack.java index 44d0d70ce..12ac52831 100644 --- a/codes/java/chapter_stack_and_queue/array_stack.java +++ b/codes/java/chapter_stack_and_queue/array_stack.java @@ -45,13 +45,6 @@ class ArrayStack { return stack.get(size() - 1); } - /* 访问索引 index 处元素 */ - public int get(int index) { - if (index >= size()) - throw new IndexOutOfBoundsException(); - return stack.get(index); - } - /* 将 List 转化为 Array 并返回 */ public Object[] toArray() { return stack.toArray(); @@ -75,10 +68,6 @@ public class array_stack { int peek = stack.peek(); System.out.println("栈顶元素 peek = " + peek); - /* 访问索引 index 处元素 */ - int num = stack.get(3); - System.out.println("栈索引 3 处的元素为 num = " + num); - /* 元素出栈 */ int pop = stack.pop(); System.out.println("出栈元素 pop = " + pop + ",出栈后 stack = " + Arrays.toString(stack.toArray())); diff --git a/codes/javascript/chapter_stack_and_queue/array_queue.js b/codes/javascript/chapter_stack_and_queue/array_queue.js index d57b7024b..abe0766d8 100644 --- a/codes/javascript/chapter_stack_and_queue/array_queue.js +++ b/codes/javascript/chapter_stack_and_queue/array_queue.js @@ -56,13 +56,6 @@ class ArrayQueue { return this.#queue[this.#front]; } - /* 访问指定索引元素 */ - get(index) { - if (index >= this.size) - throw new Error("索引越界"); - return this.#queue[(this.#front + index) % this.capacity]; - } - /* 返回 Array */ toArray() { const siz = this.size; @@ -95,10 +88,6 @@ console.log(queue.toArray()); const peek = queue.peek(); console.log("队首元素 peek = " + peek); -/* 访问指定索引元素 */ -const num = queue.get(2); -console.log("队列第 3 个元素为 num = " + num); - /* 元素出队 */ const poll = queue.poll(); console.log("出队元素 poll = " + poll + ",出队后 queue = "); diff --git a/codes/javascript/chapter_stack_and_queue/array_stack.js b/codes/javascript/chapter_stack_and_queue/array_stack.js index ba620ebd5..37fa23b1f 100644 --- a/codes/javascript/chapter_stack_and_queue/array_stack.js +++ b/codes/javascript/chapter_stack_and_queue/array_stack.js @@ -41,13 +41,6 @@ class ArrayStack { return this.stack[this.stack.length - 1]; } - /* 访问索引 index 处元素 */ - get(index) { - if (index >= this.size) - throw new Error("索引越界"); - return this.stack[index]; - } - /* 返回 Array */ toArray() { return this.stack; @@ -73,10 +66,6 @@ console.log(stack.toArray()); const top = stack.top(); console.log("栈顶元素 top = " + top); -/* 访问索引 index 处元素 */ -const num = stack.get(3); -console.log("栈索引 3 处的元素为 num = " + num); - /* 元素出栈 */ const pop = stack.pop(); console.log("出栈元素 pop = " + pop + ",出栈后 stack = "); diff --git a/codes/python/chapter_stack_and_queue/array_queue.py b/codes/python/chapter_stack_and_queue/array_queue.py index 5f3c4f3b6..7408f2ba2 100644 --- a/codes/python/chapter_stack_and_queue/array_queue.py +++ b/codes/python/chapter_stack_and_queue/array_queue.py @@ -54,13 +54,6 @@ class ArrayQueue: return False return self.__nums[self.__front] - """ 访问指定位置元素 """ - def get(self, index): - if index >= self.size(): - print("索引越界") - return False - return self.__nums[(self.__front + index) % self.capacity()] - """ 返回列表用于打印 """ def to_list(self): res = [0] * self.size() @@ -88,10 +81,6 @@ if __name__ == "__main__": peek = queue.peek() print("队首元素 peek =", peek) - """ 访问索引 index 处元素 """ - num = queue.get(3) - print("队列索引 3 处的元素为 num =", num) - """ 元素出队 """ poll = queue.poll() print("出队元素 poll =", poll) diff --git a/codes/python/chapter_stack_and_queue/array_stack.py b/codes/python/chapter_stack_and_queue/array_stack.py index 32ba9dcda..889a372dc 100644 --- a/codes/python/chapter_stack_and_queue/array_stack.py +++ b/codes/python/chapter_stack_and_queue/array_stack.py @@ -34,11 +34,6 @@ class ArrayStack: def peek(self): assert not self.is_empty(), "栈为空" return self.__stack[-1] - - """ 访问索引 index 处元素 """ - def get(self, index): - assert index < self.size(), "索引越界" - return self.__stack[index] """ 返回列表用于打印 """ def to_list(self): @@ -62,10 +57,6 @@ if __name__ == "__main__": peek = stack.peek() print("栈顶元素 peek =", peek) - """ 访问索引 index 处元素 """ - num = stack.get(3) - print("栈索引 3 处的元素为 num =", num) - """ 元素出栈 """ pop = stack.pop() print("出栈元素 pop =", pop) diff --git a/codes/typescript/chapter_stack_and_queue/array_queue.ts b/codes/typescript/chapter_stack_and_queue/array_queue.ts index 4fb72bbb3..1f67c648d 100644 --- a/codes/typescript/chapter_stack_and_queue/array_queue.ts +++ b/codes/typescript/chapter_stack_and_queue/array_queue.ts @@ -57,13 +57,6 @@ class ArrayQueue { return this.queue[this.front]; } - /* 访问指定索引元素 */ - get(index: number): number { - if (index >= this.size) - throw new Error("索引越界"); - return this.queue[(this.front + index) % this.capacity]; - } - /* 返回 Array */ toArray(): number[] { const siz = this.size; @@ -94,10 +87,6 @@ console.log(queue.toArray()); const peek = queue.peek(); console.log("队首元素 peek = " + peek); -/* 访问指定索引元素 */ -const num = queue.get(2); -console.log("队列第 3 个元素为 num = " + num); - /* 元素出队 */ const poll = queue.poll(); console.log("出队元素 poll = " + poll + ",出队后 queue = "); diff --git a/codes/typescript/chapter_stack_and_queue/array_stack.ts b/codes/typescript/chapter_stack_and_queue/array_stack.ts index ef6419f79..2b9f8468d 100644 --- a/codes/typescript/chapter_stack_and_queue/array_stack.ts +++ b/codes/typescript/chapter_stack_and_queue/array_stack.ts @@ -41,13 +41,6 @@ class ArrayStack { return this.stack[this.stack.length - 1]; } - /* 访问索引 index 处元素 */ - get(index: number): number | undefined { - if (index >= this.size) - throw new Error('索引越界'); - return this.stack[index]; - } - /* 返回 Array */ toArray() { return this.stack; @@ -73,10 +66,6 @@ console.log(stack.toArray()); const top = stack.top(); console.log("栈顶元素 top = " + top); -/* 访问索引 index 处元素 */ -const num = stack.get(3); -console.log("栈索引 3 处的元素为 num = " + num); - /* 元素出栈 */ const pop = stack.pop(); console.log("出栈元素 pop = " + pop + ",出栈后 stack = "); diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 075122427..0bcc90983 100644 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -596,12 +596,6 @@ comments: true throw new EmptyStackException(); return nums[front]; } - /* 访问指定索引元素 */ - int get(int index) { - if (index >= size()) - throw new IndexOutOfBoundsException(); - return nums[(front + index) % capacity()]; - } } ``` @@ -659,12 +653,6 @@ comments: true throw out_of_range("队列为空"); return nums[front]; } - /* 访问指定位置元素 */ - int get(int index) { - if (index >= size()) - throw out_of_range("索引越界"); - return nums[(front + index) % capacity()] - } }; ``` @@ -715,13 +703,6 @@ comments: true return False return self.__nums[self.__front] - """ 访问指定位置元素 """ - def get(self, index): - if index >= self.size(): - print("索引越界") - return False - return self.__nums[(self.__front + index) % self.capacity()] - """ 返回列表用于打印 """ def to_list(self): res = [0] * self.size() @@ -837,12 +818,6 @@ comments: true throw new Error("队列为空"); return this.#queue[this.#front]; } - /* 访问指定索引元素 */ - get(index) { - if (index >= this.size) - throw new Error("索引越界"); - return this.#queue[(this.#front + index) % this.capacity]; - } } ``` @@ -893,12 +868,6 @@ comments: true throw new Error("队列为空"); return this.queue[this.front]; } - /* 访问指定索引元素 */ - get(index: number): number { - if (index >= this.size) - throw new Error("索引越界"); - return this.queue[(this.front + index) % this.capacity]; - } } ``` diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 8a0f548e5..e53e42218 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -451,12 +451,6 @@ comments: true throw new EmptyStackException(); return stack.get(size() - 1); } - /* 访问索引 index 处元素 */ - public int get(int index) { - if (index >= size()) - throw new EmptyStackException(); - return stack.get(index); - } } ``` @@ -493,12 +487,6 @@ comments: true throw out_of_range("栈为空"); return stack.back(); } - /* 访问索引 index 处元素 */ - int get(int index) { - if(index >= size()) - throw out_of_range("索引越界"); - return stack[index]; - } }; ``` @@ -531,11 +519,6 @@ comments: true def peek(self): assert not self.is_empty(), "栈为空" return self.__stack[-1] - - """ 访问索引 index 处元素 """ - def get(self, index): - assert index < self.size(), "索引越界" - return self.__stack[index] ``` === "Go" @@ -617,12 +600,6 @@ comments: true throw new Error("栈为空"); return this.stack[this.stack.length - 1]; } - /* 访问索引 index 处元素 */ - get(index) { - if (index >= this.size) - throw new Error("索引越界"); - return this.stack[index]; - } }; ``` @@ -659,12 +636,6 @@ comments: true throw new Error('栈为空'); return this.stack[this.stack.length - 1]; } - /* 访问索引 index 处元素 */ - get(index: number): number | undefined { - if (index >= this.size) - throw new Error('索引越界'); - return this.stack[index]; - } }; ``` diff --git a/docs/chapter_tree/binary_tree_types.assets/balanced_binary_tree.png b/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png similarity index 100% rename from docs/chapter_tree/binary_tree_types.assets/balanced_binary_tree.png rename to docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png diff --git a/docs/chapter_tree/binary_tree_types.assets/complete_binary_tree.png b/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png similarity index 100% rename from docs/chapter_tree/binary_tree_types.assets/complete_binary_tree.png rename to docs/chapter_tree/binary_tree.assets/complete_binary_tree.png diff --git a/docs/chapter_tree/binary_tree_types.assets/full_binary_tree.png b/docs/chapter_tree/binary_tree.assets/full_binary_tree.png similarity index 100% rename from docs/chapter_tree/binary_tree_types.assets/full_binary_tree.png rename to docs/chapter_tree/binary_tree.assets/full_binary_tree.png diff --git a/docs/chapter_tree/binary_tree_types.assets/perfect_binary_tree.png b/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png similarity index 100% rename from docs/chapter_tree/binary_tree_types.assets/perfect_binary_tree.png rename to docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 5d4157526..f5e3b7b9d 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -79,7 +79,7 @@ comments: true val: number; left: TreeNode | null; right: TreeNode | null; - + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; // 结点值 this.left = left === undefined ? null : left; // 左子结点指针 @@ -91,13 +91,13 @@ comments: true === "C" ```c title="" - + ``` === "C#" ```csharp title="" - + ``` 结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。 @@ -129,27 +129,6 @@ comments: true 值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。 -## 二叉树最佳和最差结构 - -当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。 - -![binary_tree_corner_cases](binary_tree.assets/binary_tree_corner_cases.png) - -

Fig. 二叉树的最佳和最差结构

- -如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。 - -
- -| | 完美二叉树 | 链表 | -| ----------------------------- | ---------- | ---------- | -| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ | -| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ | -| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ | -| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | - -
- ## 二叉树基本操作 **初始化二叉树。** 与链表类似,先初始化结点,再构建引用指向(即指针)。 @@ -190,7 +169,7 @@ comments: true === "Python" ```python title="binary_tree.py" - + ``` === "Go" @@ -247,13 +226,13 @@ comments: true === "C" ```c title="binary_tree.c" - + ``` === "C#" ```csharp title="binary_tree.cs" - + ``` **插入与删除结点。** 与链表类似,插入与删除结点都可以通过修改指针实现。 @@ -288,7 +267,7 @@ comments: true === "Python" ```python title="binary_tree.py" - + ``` === "Go" @@ -330,374 +309,71 @@ comments: true === "C" ```c title="binary_tree.c" - + ``` === "C#" ```csharp title="binary_tree.cs" - + ``` !!! note 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 -## 二叉树遍历 +## 常见二叉树类型 -非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。 +### 完美二叉树 -### 层序遍历 +「完美二叉树 Perfect Binary Tree」的所有层的结点都被完全填满。在完美二叉树中,所有结点的度 = 2 ;若树高度 $= h$ ,则结点总数 $= 2^{h+1} - 1$ ,呈标准的指数级关系,反映着自然界中常见的细胞分裂。 -「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。 +!!! tip -层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。 + 在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。 -![binary_tree_bfs](binary_tree.assets/binary_tree_bfs.png) +![perfect_binary_tree](binary_tree.assets/perfect_binary_tree.png) -

Fig. 二叉树的层序遍历

+### 完全二叉树 -广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。 +「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。 -=== "Java" +**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。 - ```java title="binary_tree_bfs.java" - /* 层序遍历 */ - List hierOrder(TreeNode root) { - // 初始化队列,加入根结点 - Queue queue = new LinkedList<>() {{ 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; - } - ``` +![complete_binary_tree](binary_tree.assets/complete_binary_tree.png) -=== "C++" +### 完满二叉树 - ```cpp title="binary_tree_bfs.cpp" - /* 层序遍历 */ - vector hierOrder(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; - } - ``` +「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。 -=== "Python" +![full_binary_tree](binary_tree.assets/full_binary_tree.png) - ```python title="binary_tree_bfs.py" +### 平衡二叉树 - ``` +「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。 -=== "Go" +![balanced_binary_tree](binary_tree.assets/balanced_binary_tree.png) - ```go title="binary_tree_bfs.go" - /* 层序遍历 */ - func levelOrder(root *TreeNode) []int { - // 初始化队列,加入根结点 - queue := list.New() - queue.PushBack(root) - // 初始化一个切片,用于保存遍历序列 - nums := make([]int, 0) - for queue.Len() > 0 { - // poll - 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 - } - ``` +## 二叉树的退化 -=== "JavaScript" +当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。 - ```js title="binary_tree_bfs.js" - /* 层序遍历 */ - function hierOrder(root) { - // 初始化队列,加入根结点 - let queue = [root]; - // 初始化一个列表,用于保存遍历序列 - let 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; - } - ``` +- 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势; +- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ; -=== "TypeScript" +![binary_tree_corner_cases](binary_tree.assets/binary_tree_corner_cases.png) - ```typescript title="binary_tree_bfs.ts" - /* 层序遍历 */ - function hierOrder(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; - } - ``` +

Fig. 二叉树的最佳和最差结构

-=== "C" - - ```c title="binary_tree_bfs.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree_bfs.cs" - - ``` - -### 前序、中序、后序遍历 - -相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 - -如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。 - -![binary_tree_dfs](binary_tree.assets/binary_tree_dfs.png) - -

Fig. 二叉树的前 / 中 / 后序遍历

+如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
-| 位置 | 含义 | 此处访问结点时对应 | -| ---------- | ------------------------------------ | ----------------------------- | -| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal | -| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal | -| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal | +| | 完美二叉树 | 链表 | +| ----------------------------- | ---------- | ---------- | +| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ | +| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ | +| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ | +| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ |
- -=== "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++" - - ```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); - } - ``` - -=== "Python" - - ```python title="binary_tree_dfs.py" - - ``` - -=== "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) - } - ``` - -=== "JavaScript" - - ```js 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); - } - ``` - -=== "TypeScript" - - ```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); - } - ``` - -=== "C" - - ```c title="binary_tree_dfs.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree_dfs.cs" - - ``` - -!!! note - - 使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。 diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_bfs.png b/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png similarity index 100% rename from docs/chapter_tree/binary_tree.assets/binary_tree_bfs.png rename to docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_dfs.png b/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png similarity index 100% rename from docs/chapter_tree/binary_tree.assets/binary_tree_dfs.png rename to docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md new file mode 100644 index 000000000..129d297c6 --- /dev/null +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -0,0 +1,362 @@ +--- +comments: true +--- + +# 二叉树遍历 + +非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。 + +## 层序遍历 + +「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。 + +层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。 + +![binary_tree_bfs](binary_tree_traversal.assets/binary_tree_bfs.png) + +

Fig. 二叉树的层序遍历

+ +广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。 + +=== "Java" + + ```java title="binary_tree_bfs.java" + /* 层序遍历 */ + List hierOrder(TreeNode root) { + // 初始化队列,加入根结点 + Queue queue = new LinkedList<>() {{ 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++" + + ```cpp title="binary_tree_bfs.cpp" + /* 层序遍历 */ + vector hierOrder(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; + } + ``` + +=== "Python" + + ```python title="binary_tree_bfs.py" + + ``` + +=== "Go" + + ```go title="binary_tree_bfs.go" + /* 层序遍历 */ + func levelOrder(root *TreeNode) []int { + // 初始化队列,加入根结点 + queue := list.New() + queue.PushBack(root) + // 初始化一个切片,用于保存遍历序列 + nums := make([]int, 0) + for queue.Len() > 0 { + // poll + 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 + } + ``` + +=== "JavaScript" + + ```js title="binary_tree_bfs.js" + /* 层序遍历 */ + function hierOrder(root) { + // 初始化队列,加入根结点 + let queue = [root]; + // 初始化一个列表,用于保存遍历序列 + let 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; + } + ``` + +=== "TypeScript" + + ```typescript title="binary_tree_bfs.ts" + /* 层序遍历 */ + function hierOrder(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; + } + ``` + +=== "C" + + ```c title="binary_tree_bfs.c" + + ``` + +=== "C#" + + ```csharp title="binary_tree_bfs.cs" + + ``` + +## 前序、中序、后序遍历 + +相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 + +如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。 + +![binary_tree_dfs](binary_tree_traversal.assets/binary_tree_dfs.png) + +

Fig. 二叉树的前 / 中 / 后序遍历

+ +
+ +| 位置 | 含义 | 此处访问结点时对应 | +| ---------- | ------------------------------------ | ----------------------------- | +| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal | +| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal | +| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal | + +
+ +=== "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++" + + ```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); + } + ``` + +=== "Python" + + ```python title="binary_tree_dfs.py" + + ``` + +=== "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) + } + ``` + +=== "JavaScript" + + ```js 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); + } + ``` + +=== "TypeScript" + + ```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); + } + ``` + +=== "C" + + ```c title="binary_tree_dfs.c" + + ``` + +=== "C#" + + ```csharp title="binary_tree_dfs.cs" + + ``` + +!!! note + + 使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。 diff --git a/docs/chapter_tree/binary_tree_types.md b/docs/chapter_tree/binary_tree_types.md deleted file mode 100644 index b5879b4ec..000000000 --- a/docs/chapter_tree/binary_tree_types.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -comments: true ---- - -# 常见二叉树类型 - -## 完美二叉树 - -「完美二叉树 Perfect Binary Tree」,其所有层的结点都被完全填满。 - -!!! tip - - 在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。 - -![perfect_binary_tree](binary_tree_types.assets/perfect_binary_tree.png) - -完美二叉树的性质有: - -- 若树高度 $= h$ ,则结点总数 $= 2^h - 1$; -- (TODO) - -## 完全二叉树 - -「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点都尽量靠左填充。 - -![complete_binary_tree](binary_tree_types.assets/complete_binary_tree.png) - -完全二叉树有一个很好的性质,可以用「数组」来表示。 - -- (TODO) - -## 完满二叉树 - -「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。 - -![full_binary_tree](binary_tree_types.assets/full_binary_tree.png) - -## 平衡二叉树 - -**「平衡二叉树 Balanced Binary Tree」** ,其任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。 - -![balanced_binary_tree](binary_tree_types.assets/balanced_binary_tree.png) - -- (TODO) diff --git a/mkdocs.yml b/mkdocs.yml index 47406412b..4334382f4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -157,7 +157,7 @@ nav: - 小结: chapter_hashing/summary.md - 二叉树: - 二叉树(Binary Tree): chapter_tree/binary_tree.md - - 二叉树常见类型: chapter_tree/binary_tree_types.md + - 二叉树遍历: chapter_tree/binary_tree_traversal.md - 二叉搜索树: chapter_tree/binary_search_tree.md - AVL 树 *: chapter_tree/avl_tree.md - 小结: chapter_tree/summary.md