Update the structure of the chapter
of binary tree.
@ -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 = ";
|
||||
|
@ -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 = ";
|
||||
|
@ -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();
|
||||
|
@ -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()));
|
||||
|
@ -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 = ");
|
||||
|
@ -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 = ");
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 = ");
|
||||
|
@ -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 = ");
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
@ -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
|
||||
|
||||
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。
|
||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
362
docs/chapter_tree/binary_tree_traversal.md
Normal 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
|
||||
|
||||
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。
|
@ -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)
|
@ -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
|
||||
|