Update the structure of the chapter
of binary tree.
@ -64,13 +64,6 @@ public:
|
|||||||
return nums[front];
|
return nums[front];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问指定索引元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw out_of_range("索引越界");
|
|
||||||
return nums[(front + index) % capacity()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 将数组转化为 Vector 并返回 */
|
/* 将数组转化为 Vector 并返回 */
|
||||||
vector<int> toVector() {
|
vector<int> toVector() {
|
||||||
int siz = size();
|
int siz = size();
|
||||||
@ -103,11 +96,7 @@ int main() {
|
|||||||
/* 访问队首元素 */
|
/* 访问队首元素 */
|
||||||
int peek = queue->peek();
|
int peek = queue->peek();
|
||||||
cout << "队首元素 peek = " << peek << endl;
|
cout << "队首元素 peek = " << peek << endl;
|
||||||
|
|
||||||
/* 访问指定索引元素 */
|
|
||||||
int num = queue->get(2);
|
|
||||||
cout << "队列第 3 个元素为 num = " << num << endl;
|
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
int poll = queue->poll();
|
int poll = queue->poll();
|
||||||
cout << "出队元素 poll = " << poll << ",出队后 queue = ";
|
cout << "出队元素 poll = " << poll << ",出队后 queue = ";
|
||||||
|
@ -41,13 +41,6 @@ public:
|
|||||||
return stack.back();
|
return stack.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if(index >= size())
|
|
||||||
throw out_of_range("索引越界");
|
|
||||||
return stack[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回 Vector */
|
/* 返回 Vector */
|
||||||
vector<int> toVector() {
|
vector<int> toVector() {
|
||||||
return stack;
|
return stack;
|
||||||
@ -73,10 +66,6 @@ int main() {
|
|||||||
int top = stack->top();
|
int top = stack->top();
|
||||||
cout << "栈顶元素 top = " << top << endl;
|
cout << "栈顶元素 top = " << top << endl;
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int num = stack->get(3);
|
|
||||||
cout << "栈索引 3 处的元素为 num = " << num << endl;
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
int pop = stack->pop();
|
int pop = stack->pop();
|
||||||
cout << "出栈元素 pop = " << pop << ",出栈后 stack = ";
|
cout << "出栈元素 pop = " << pop << ",出栈后 stack = ";
|
||||||
|
@ -63,13 +63,6 @@ class ArrayQueue {
|
|||||||
return nums[front];
|
return nums[front];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
return nums[(front + index) % capacity()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回数组 */
|
/* 返回数组 */
|
||||||
public int[] toArray() {
|
public int[] toArray() {
|
||||||
int size = size();
|
int size = size();
|
||||||
|
@ -45,13 +45,6 @@ class ArrayStack {
|
|||||||
return stack.get(size() - 1);
|
return stack.get(size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
public int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
return stack.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 将 List 转化为 Array 并返回 */
|
/* 将 List 转化为 Array 并返回 */
|
||||||
public Object[] toArray() {
|
public Object[] toArray() {
|
||||||
return stack.toArray();
|
return stack.toArray();
|
||||||
@ -75,10 +68,6 @@ public class array_stack {
|
|||||||
int peek = stack.peek();
|
int peek = stack.peek();
|
||||||
System.out.println("栈顶元素 peek = " + peek);
|
System.out.println("栈顶元素 peek = " + peek);
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int num = stack.get(3);
|
|
||||||
System.out.println("栈索引 3 处的元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
int pop = stack.pop();
|
int pop = stack.pop();
|
||||||
System.out.println("出栈元素 pop = " + pop + ",出栈后 stack = " + Arrays.toString(stack.toArray()));
|
System.out.println("出栈元素 pop = " + pop + ",出栈后 stack = " + Arrays.toString(stack.toArray()));
|
||||||
|
@ -56,13 +56,6 @@ class ArrayQueue {
|
|||||||
return this.#queue[this.#front];
|
return this.#queue[this.#front];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问指定索引元素 */
|
|
||||||
get(index) {
|
|
||||||
if (index >= this.size)
|
|
||||||
throw new Error("索引越界");
|
|
||||||
return this.#queue[(this.#front + index) % this.capacity];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回 Array */
|
/* 返回 Array */
|
||||||
toArray() {
|
toArray() {
|
||||||
const siz = this.size;
|
const siz = this.size;
|
||||||
@ -95,10 +88,6 @@ console.log(queue.toArray());
|
|||||||
const peek = queue.peek();
|
const peek = queue.peek();
|
||||||
console.log("队首元素 peek = " + peek);
|
console.log("队首元素 peek = " + peek);
|
||||||
|
|
||||||
/* 访问指定索引元素 */
|
|
||||||
const num = queue.get(2);
|
|
||||||
console.log("队列第 3 个元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
const poll = queue.poll();
|
const poll = queue.poll();
|
||||||
console.log("出队元素 poll = " + poll + ",出队后 queue = ");
|
console.log("出队元素 poll = " + poll + ",出队后 queue = ");
|
||||||
|
@ -41,13 +41,6 @@ class ArrayStack {
|
|||||||
return this.stack[this.stack.length - 1];
|
return this.stack[this.stack.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
get(index) {
|
|
||||||
if (index >= this.size)
|
|
||||||
throw new Error("索引越界");
|
|
||||||
return this.stack[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回 Array */
|
/* 返回 Array */
|
||||||
toArray() {
|
toArray() {
|
||||||
return this.stack;
|
return this.stack;
|
||||||
@ -73,10 +66,6 @@ console.log(stack.toArray());
|
|||||||
const top = stack.top();
|
const top = stack.top();
|
||||||
console.log("栈顶元素 top = " + top);
|
console.log("栈顶元素 top = " + top);
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
const num = stack.get(3);
|
|
||||||
console.log("栈索引 3 处的元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
const pop = stack.pop();
|
const pop = stack.pop();
|
||||||
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
||||||
|
@ -54,13 +54,6 @@ class ArrayQueue:
|
|||||||
return False
|
return False
|
||||||
return self.__nums[self.__front]
|
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):
|
def to_list(self):
|
||||||
res = [0] * self.size()
|
res = [0] * self.size()
|
||||||
@ -88,10 +81,6 @@ if __name__ == "__main__":
|
|||||||
peek = queue.peek()
|
peek = queue.peek()
|
||||||
print("队首元素 peek =", peek)
|
print("队首元素 peek =", peek)
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
num = queue.get(3)
|
|
||||||
print("队列索引 3 处的元素为 num =", num)
|
|
||||||
|
|
||||||
""" 元素出队 """
|
""" 元素出队 """
|
||||||
poll = queue.poll()
|
poll = queue.poll()
|
||||||
print("出队元素 poll =", poll)
|
print("出队元素 poll =", poll)
|
||||||
|
@ -34,11 +34,6 @@ class ArrayStack:
|
|||||||
def peek(self):
|
def peek(self):
|
||||||
assert not self.is_empty(), "栈为空"
|
assert not self.is_empty(), "栈为空"
|
||||||
return self.__stack[-1]
|
return self.__stack[-1]
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
def get(self, index):
|
|
||||||
assert index < self.size(), "索引越界"
|
|
||||||
return self.__stack[index]
|
|
||||||
|
|
||||||
""" 返回列表用于打印 """
|
""" 返回列表用于打印 """
|
||||||
def to_list(self):
|
def to_list(self):
|
||||||
@ -62,10 +57,6 @@ if __name__ == "__main__":
|
|||||||
peek = stack.peek()
|
peek = stack.peek()
|
||||||
print("栈顶元素 peek =", peek)
|
print("栈顶元素 peek =", peek)
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
num = stack.get(3)
|
|
||||||
print("栈索引 3 处的元素为 num =", num)
|
|
||||||
|
|
||||||
""" 元素出栈 """
|
""" 元素出栈 """
|
||||||
pop = stack.pop()
|
pop = stack.pop()
|
||||||
print("出栈元素 pop =", pop)
|
print("出栈元素 pop =", pop)
|
||||||
|
@ -57,13 +57,6 @@ class ArrayQueue {
|
|||||||
return this.queue[this.front];
|
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 */
|
/* 返回 Array */
|
||||||
toArray(): number[] {
|
toArray(): number[] {
|
||||||
const siz = this.size;
|
const siz = this.size;
|
||||||
@ -94,10 +87,6 @@ console.log(queue.toArray());
|
|||||||
const peek = queue.peek();
|
const peek = queue.peek();
|
||||||
console.log("队首元素 peek = " + peek);
|
console.log("队首元素 peek = " + peek);
|
||||||
|
|
||||||
/* 访问指定索引元素 */
|
|
||||||
const num = queue.get(2);
|
|
||||||
console.log("队列第 3 个元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
const poll = queue.poll();
|
const poll = queue.poll();
|
||||||
console.log("出队元素 poll = " + poll + ",出队后 queue = ");
|
console.log("出队元素 poll = " + poll + ",出队后 queue = ");
|
||||||
|
@ -41,13 +41,6 @@ class ArrayStack {
|
|||||||
return this.stack[this.stack.length - 1];
|
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 */
|
/* 返回 Array */
|
||||||
toArray() {
|
toArray() {
|
||||||
return this.stack;
|
return this.stack;
|
||||||
@ -73,10 +66,6 @@ console.log(stack.toArray());
|
|||||||
const top = stack.top();
|
const top = stack.top();
|
||||||
console.log("栈顶元素 top = " + top);
|
console.log("栈顶元素 top = " + top);
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
const num = stack.get(3);
|
|
||||||
console.log("栈索引 3 处的元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
const pop = stack.pop();
|
const pop = stack.pop();
|
||||||
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
||||||
|
@ -596,12 +596,6 @@ comments: true
|
|||||||
throw new EmptyStackException();
|
throw new EmptyStackException();
|
||||||
return nums[front];
|
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("队列为空");
|
throw out_of_range("队列为空");
|
||||||
return nums[front];
|
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 False
|
||||||
return self.__nums[self.__front]
|
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):
|
def to_list(self):
|
||||||
res = [0] * self.size()
|
res = [0] * self.size()
|
||||||
@ -837,12 +818,6 @@ comments: true
|
|||||||
throw new Error("队列为空");
|
throw new Error("队列为空");
|
||||||
return this.#queue[this.#front];
|
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("队列为空");
|
throw new Error("队列为空");
|
||||||
return this.queue[this.front];
|
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();
|
throw new EmptyStackException();
|
||||||
return stack.get(size() - 1);
|
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("栈为空");
|
throw out_of_range("栈为空");
|
||||||
return stack.back();
|
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):
|
def peek(self):
|
||||||
assert not self.is_empty(), "栈为空"
|
assert not self.is_empty(), "栈为空"
|
||||||
return self.__stack[-1]
|
return self.__stack[-1]
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
def get(self, index):
|
|
||||||
assert index < self.size(), "索引越界"
|
|
||||||
return self.__stack[index]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Go"
|
=== "Go"
|
||||||
@ -617,12 +600,6 @@ comments: true
|
|||||||
throw new Error("栈为空");
|
throw new Error("栈为空");
|
||||||
return this.stack[this.stack.length - 1];
|
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('栈为空');
|
throw new Error('栈为空');
|
||||||
return this.stack[this.stack.length - 1];
|
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;
|
val: number;
|
||||||
left: TreeNode | null;
|
left: TreeNode | null;
|
||||||
right: TreeNode | null;
|
right: TreeNode | null;
|
||||||
|
|
||||||
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
|
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
|
||||||
this.val = val === undefined ? 0 : val; // 结点值
|
this.val = val === undefined ? 0 : val; // 结点值
|
||||||
this.left = left === undefined ? null : left; // 左子结点指针
|
this.left = left === undefined ? null : left; // 左子结点指针
|
||||||
@ -91,13 +91,13 @@ comments: true
|
|||||||
=== "C"
|
=== "C"
|
||||||
|
|
||||||
```c title=""
|
```c title=""
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "C#"
|
=== "C#"
|
||||||
|
|
||||||
```csharp title=""
|
```csharp title=""
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。
|
结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。
|
||||||
@ -129,27 +129,6 @@ comments: true
|
|||||||
|
|
||||||
值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。
|
值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 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"
|
||||||
|
|
||||||
```python title="binary_tree.py"
|
```python title="binary_tree.py"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Go"
|
=== "Go"
|
||||||
@ -247,13 +226,13 @@ comments: true
|
|||||||
=== "C"
|
=== "C"
|
||||||
|
|
||||||
```c title="binary_tree.c"
|
```c title="binary_tree.c"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "C#"
|
=== "C#"
|
||||||
|
|
||||||
```csharp title="binary_tree.cs"
|
```csharp title="binary_tree.cs"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**插入与删除结点。** 与链表类似,插入与删除结点都可以通过修改指针实现。
|
**插入与删除结点。** 与链表类似,插入与删除结点都可以通过修改指针实现。
|
||||||
@ -288,7 +267,7 @@ comments: true
|
|||||||
=== "Python"
|
=== "Python"
|
||||||
|
|
||||||
```python title="binary_tree.py"
|
```python title="binary_tree.py"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Go"
|
=== "Go"
|
||||||
@ -330,374 +309,71 @@ comments: true
|
|||||||
=== "C"
|
=== "C"
|
||||||
|
|
||||||
```c title="binary_tree.c"
|
```c title="binary_tree.c"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "C#"
|
=== "C#"
|
||||||
|
|
||||||
```csharp title="binary_tree.cs"
|
```csharp title="binary_tree.cs"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
!!! 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"
|
![complete_binary_tree](binary_tree.assets/complete_binary_tree.png)
|
||||||
/* 层序遍历 */
|
|
||||||
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"
|
「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。
|
||||||
/* 层序遍历 */
|
|
||||||
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"
|
![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"
|
- 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势;
|
||||||
/* 层序遍历 */
|
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ;
|
||||||
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"
|
![binary_tree_corner_cases](binary_tree.assets/binary_tree_corner_cases.png)
|
||||||
|
|
||||||
```typescript title="binary_tree_bfs.ts"
|
<p align="center"> Fig. 二叉树的最佳和最差结构 </p>
|
||||||
/* 层序遍历 */
|
|
||||||
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.assets/binary_tree_dfs.png)
|
|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的前 / 中 / 后序遍历 </p>
|
|
||||||
|
|
||||||
<div class="center-table" markdown>
|
<div class="center-table" markdown>
|
||||||
|
|
||||||
| 位置 | 含义 | 此处访问结点时对应 |
|
| | 完美二叉树 | 链表 |
|
||||||
| ---------- | ------------------------------------ | ----------------------------- |
|
| ----------------------------- | ---------- | ---------- |
|
||||||
| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal |
|
| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ |
|
||||||
| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal |
|
| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ |
|
||||||
| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal |
|
| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ |
|
||||||
|
| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ |
|
||||||
|
|
||||||
</div>
|
</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
|
- 小结: chapter_hashing/summary.md
|
||||||
- 二叉树:
|
- 二叉树:
|
||||||
- 二叉树(Binary Tree): chapter_tree/binary_tree.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
|
- 二叉搜索树: chapter_tree/binary_search_tree.md
|
||||||
- AVL 树 *: chapter_tree/avl_tree.md
|
- AVL 树 *: chapter_tree/avl_tree.md
|
||||||
- 小结: chapter_tree/summary.md
|
- 小结: chapter_tree/summary.md
|
||||||
|