Update the structure of the chapter

of binary tree.
This commit is contained in:
Yudong Jin 2022-12-21 17:19:39 +08:00
parent f3ef226874
commit f39636cb63
22 changed files with 402 additions and 572 deletions

View File

@ -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<int> 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 = ";

View File

@ -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<int> 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 = ";

View File

@ -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();

View File

@ -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()));

View File

@ -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 = ");

View File

@ -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 = ");

View File

@ -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)

View File

@ -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)

View File

@ -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 = ");

View File

@ -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 = ");

View File

@ -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];
}
}
```

View File

@ -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];
}
};
```

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -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)
<p align="center"> Fig. 二叉树的最佳和最差结构 </p>
如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
<div class="center-table" markdown>
| | 完美二叉树 | 链表 |
| ----------------------------- | ---------- | ---------- |
| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ |
| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ |
| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ |
| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ |
</div>
## 二叉树基本操作
**初始化二叉树。** 与链表类似,先初始化结点,再构建引用指向(即指针)。
@ -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)
<p align="center"> Fig. 二叉树的层序遍历 </p>
### 完全二叉树
广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的
「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满且最底层结点尽量靠左填充
=== "Java"
**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。
```java title="binary_tree_bfs.java"
/* 层序遍历 */
List<Integer> hierOrder(TreeNode root) {
// 初始化队列,加入根结点
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
// 初始化一个列表,用于保存遍历序列
List<Integer> 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<int> hierOrder(TreeNode* root) {
// 初始化队列,加入根结点
queue<TreeNode*> queue;
queue.push(root);
// 初始化一个列表,用于保存遍历序列
vector<int> 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;
}
```
<p align="center"> Fig. 二叉树的最佳和最差结构 </p>
=== "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)
<p align="center"> Fig. 二叉树的前 / 中 / 后序遍历 </p>
如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
<div class="center-table" markdown>
| 位置 | 含义 | 此处访问结点时对应 |
| ---------- | ------------------------------------ | ----------------------------- |
| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 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$ |
</div>
=== "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
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View File

@ -0,0 +1,362 @@
---
comments: true
---
# 二叉树遍历
非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。
## 层序遍历
「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树并在每层中按照从左到右的顺序访问结点。
层序遍历本质上是「广度优先搜索 Breadth-First Traversal」其体现着一种“一圈一圈向外”的层进遍历方式。
![binary_tree_bfs](binary_tree_traversal.assets/binary_tree_bfs.png)
<p align="center"> Fig. 二叉树的层序遍历 </p>
广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。
=== "Java"
```java title="binary_tree_bfs.java"
/* 层序遍历 */
List<Integer> hierOrder(TreeNode root) {
// 初始化队列,加入根结点
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
// 初始化一个列表,用于保存遍历序列
List<Integer> 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<int> hierOrder(TreeNode* root) {
// 初始化队列,加入根结点
queue<TreeNode*> queue;
queue.push(root);
// 初始化一个列表,用于保存遍历序列
vector<int> 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)
<p align="center"> Fig. 二叉树的前 / 中 / 后序遍历 </p>
<div class="center-table" markdown>
| 位置 | 含义 | 此处访问结点时对应 |
| ---------- | ------------------------------------ | ----------------------------- |
| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal |
| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal |
| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal |
</div>
=== "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
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。

View File

@ -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

View File

@ -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