This commit is contained in:
krahets
2026-03-29 02:26:00 +08:00
parent 63276d36d9
commit 37523d4ceb
118 changed files with 74250 additions and 21 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,698 @@
---
comments: true
---
# 7.1   Двоичное дерево
<u>Двоичное дерево (binary tree)</u> - это нелинейная структура данных, представляющая отношения порождения между "предками" и "потомками" и отражающая логику "разделения надвое". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.
=== "Python"
```python title=""
class TreeNode:
"""Класс узла двоичного дерева"""
def __init__(self, val: int):
self.val: int = val # Значение узла
self.left: TreeNode | None = None # Ссылка на левого дочернего узла
self.right: TreeNode | None = None # Ссылка на правого дочернего узла
```
=== "C++"
```cpp title=""
/* Структура узла двоичного дерева */
struct TreeNode {
int val; // Значение узла
TreeNode *left; // Указатель на левого дочернего узла
TreeNode *right; // Указатель на правого дочернего узла
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
```
=== "Java"
```java title=""
/* Класс узла двоичного дерева */
class TreeNode {
int val; // Значение узла
TreeNode left; // Ссылка на левого дочернего узла
TreeNode right; // Ссылка на правого дочернего узла
TreeNode(int x) { val = x; }
}
```
=== "C#"
```csharp title=""
/* Класс узла двоичного дерева */
class TreeNode(int? x) {
public int? val = x; // Значение узла
public TreeNode? left; // Ссылка на левого дочернего узла
public TreeNode? right; // Ссылка на правого дочернего узла
}
```
=== "Go"
```go title=""
/* Структура узла двоичного дерева */
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
/* Конструктор */
func NewTreeNode(v int) *TreeNode {
return &TreeNode{
Left: nil, // Указатель на левого дочернего узла
Right: nil, // Указатель на правого дочернего узла
Val: v, // Значение узла
}
}
```
=== "Swift"
```swift title=""
/* Класс узла двоичного дерева */
class TreeNode {
var val: Int // Значение узла
var left: TreeNode? // Ссылка на левого дочернего узла
var right: TreeNode? // Ссылка на правого дочернего узла
init(x: Int) {
val = x
}
}
```
=== "JS"
```javascript title=""
/* Класс узла двоичного дерева */
class TreeNode {
val; // Значение узла
left; // Указатель на левого дочернего узла
right; // Указатель на правого дочернего узла
constructor(val, left, right) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
```
=== "TS"
```typescript title=""
/* Класс узла двоичного дерева */
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val; // Значение узла
this.left = left === undefined ? null : left; // Ссылка на левого дочернего узла
this.right = right === undefined ? null : right; // Ссылка на правого дочернего узла
}
}
```
=== "Dart"
```dart title=""
/* Класс узла двоичного дерева */
class TreeNode {
int val; // Значение узла
TreeNode? left; // Ссылка на левого дочернего узла
TreeNode? right; // Ссылка на правого дочернего узла
TreeNode(this.val, [this.left, this.right]);
}
```
=== "Rust"
```rust title=""
use std::rc::Rc;
use std::cell::RefCell;
/* Структура узла двоичного дерева */
struct TreeNode {
val: i32, // Значение узла
left: Option<Rc<RefCell<TreeNode>>>, // Ссылка на левого дочернего узла
right: Option<Rc<RefCell<TreeNode>>>, // Ссылка на правого дочернего узла
}
impl TreeNode {
/* Конструктор */
fn new(val: i32) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
val,
left: None,
right: None
}))
}
}
```
=== "C"
```c title=""
/* Структура узла двоичного дерева */
typedef struct TreeNode {
int val; // Значение узла
int height; // Высота узла
struct TreeNode *left; // Указатель на левого дочернего узла
struct TreeNode *right; // Указатель на правого дочернего узла
} TreeNode;
/* Конструктор */
TreeNode *newTreeNode(int val) {
TreeNode *node;
node = (TreeNode *)malloc(sizeof(TreeNode));
node->val = val;
node->height = 0;
node->left = NULL;
node->right = NULL;
return node;
}
```
=== "Kotlin"
```kotlin title=""
/* Класс узла двоичного дерева */
class TreeNode(val _val: Int) { // Значение узла
val left: TreeNode? = null // Ссылка на левого дочернего узла
val right: TreeNode? = null // Ссылка на правого дочернего узла
}
```
=== "Ruby"
```ruby title=""
### Класс узла двоичного дерева ###
class TreeNode
attr_accessor :val # Значение узла
attr_accessor :left # Ссылка на левого дочернего узла
attr_accessor :right # Ссылка на правого дочернего узла
def initialize(val)
@val = val
end
end
```
Каждый узел имеет две ссылки (указателя), которые соответственно указывают на <u>левого дочернего узла (left-child node)</u> и <u>правого дочернего узла (right-child node)</u>; данный узел называется <u>родительским узлом (parent node)</u> для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется <u>левым поддеревом (left subtree)</u> этого узла; аналогично определяется <u>правое поддерево (right subtree)</u>.
**В двоичном дереве, кроме листовых узлов, все остальные узлы содержат дочерние узлы и непустые поддеревья**. Как показано на рисунке 7-1, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него".
![Родительский узел, дочерние узлы и поддеревья](binary_tree.assets/binary_tree_definition.png){ class="animation-figure" }
<p align="center"> Рисунок 7-1 &nbsp; Родительский узел, дочерние узлы и поддеревья </p>
## 7.1.1 &nbsp; Распространенные термины двоичного дерева
Распространенные термины двоичного дерева показаны на рисунке 7-2.
- <u>Корневой узел (root node)</u>: узел, расположенный на верхнем уровне двоичного дерева и не имеющий родительского узла.
- <u>Листовой узел (leaf node)</u>: узел без дочерних узлов; оба его указателя направлены на `None` .
- <u>Ребро (edge)</u>: отрезок, соединяющий два узла, то есть ссылка (указатель) между узлами.
- <u>Уровень (level)</u> узла: увеличивается сверху вниз; уровень корневого узла равен 1 .
- <u>Степень (degree)</u> узла: число дочерних узлов данного узла. В двоичном дереве возможны степени 0, 1, 2 .
- <u>Высота (height)</u> двоичного дерева: число ребер от корневого узла до самого удаленного листового узла.
- <u>Глубина (depth)</u> узла: число ребер от корневого узла до данного узла.
- <u>Высота (height)</u> узла: число ребер от самого удаленного листового узла до данного узла.
![Распространенные термины двоичного дерева](binary_tree.assets/binary_tree_terminology.png){ class="animation-figure" }
<p align="center"> Рисунок 7-2 &nbsp; Распространенные термины двоичного дерева </p>
!!! tip
Обрати внимание: обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 .
## 7.1.2 &nbsp; Базовые операции двоичного дерева
### 1. &nbsp; Инициализация двоичного дерева
Как и в связном списке, сначала инициализируются узлы, а затем между ними строятся ссылки (указатели).
=== "Python"
```python title="binary_tree.py"
# Инициализация двоичного дерева
# Инициализация узлов
n1 = TreeNode(val=1)
n2 = TreeNode(val=2)
n3 = TreeNode(val=3)
n4 = TreeNode(val=4)
n5 = TreeNode(val=5)
# Построение ссылок (указателей) между узлами
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
```
=== "C++"
```cpp title="binary_tree.cpp"
/* Инициализация двоичного дерева */
// Инициализация узлов
TreeNode* n1 = new TreeNode(1);
TreeNode* n2 = new TreeNode(2);
TreeNode* n3 = new TreeNode(3);
TreeNode* n4 = new TreeNode(4);
TreeNode* n5 = new TreeNode(5);
// Построение ссылок (указателей) между узлами
n1->left = n2;
n1->right = n3;
n2->left = n4;
n2->right = n5;
```
=== "Java"
```java title="binary_tree.java"
// Инициализация узлов
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
// Построение ссылок (указателей) между узлами
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
```
=== "C#"
```csharp title="binary_tree.cs"
/* Инициализация двоичного дерева */
// Инициализация узлов
TreeNode n1 = new(1);
TreeNode n2 = new(2);
TreeNode n3 = new(3);
TreeNode n4 = new(4);
TreeNode n5 = new(5);
// Построение ссылок (указателей) между узлами
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
```
=== "Go"
```go title="binary_tree.go"
/* Инициализация двоичного дерева */
// Инициализация узлов
n1 := NewTreeNode(1)
n2 := NewTreeNode(2)
n3 := NewTreeNode(3)
n4 := NewTreeNode(4)
n5 := NewTreeNode(5)
// Построение ссылок (указателей) между узлами
n1.Left = n2
n1.Right = n3
n2.Left = n4
n2.Right = n5
```
=== "Swift"
```swift title="binary_tree.swift"
// Инициализация узлов
let n1 = TreeNode(x: 1)
let n2 = TreeNode(x: 2)
let n3 = TreeNode(x: 3)
let n4 = TreeNode(x: 4)
let n5 = TreeNode(x: 5)
// Построение ссылок (указателей) между узлами
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
```
=== "JS"
```javascript title="binary_tree.js"
/* Инициализация двоичного дерева */
// Инициализация узлов
let n1 = new TreeNode(1),
n2 = new TreeNode(2),
n3 = new TreeNode(3),
n4 = new TreeNode(4),
n5 = new TreeNode(5);
// Построение ссылок (указателей) между узлами
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
```
=== "TS"
```typescript title="binary_tree.ts"
/* Инициализация двоичного дерева */
// Инициализация узлов
let n1 = new TreeNode(1),
n2 = new TreeNode(2),
n3 = new TreeNode(3),
n4 = new TreeNode(4),
n5 = new TreeNode(5);
// Построение ссылок (указателей) между узлами
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
```
=== "Dart"
```dart title="binary_tree.dart"
/* Инициализация двоичного дерева */
// Инициализация узлов
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
// Построение ссылок (указателей) между узлами
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
```
=== "Rust"
```rust title="binary_tree.rs"
// Инициализация узлов
let n1 = TreeNode::new(1);
let n2 = TreeNode::new(2);
let n3 = TreeNode::new(3);
let n4 = TreeNode::new(4);
let n5 = TreeNode::new(5);
// Построение ссылок (указателей) между узлами
n1.borrow_mut().left = Some(n2.clone());
n1.borrow_mut().right = Some(n3);
n2.borrow_mut().left = Some(n4);
n2.borrow_mut().right = Some(n5);
```
=== "C"
```c title="binary_tree.c"
/* Инициализация двоичного дерева */
// Инициализация узлов
TreeNode *n1 = newTreeNode(1);
TreeNode *n2 = newTreeNode(2);
TreeNode *n3 = newTreeNode(3);
TreeNode *n4 = newTreeNode(4);
TreeNode *n5 = newTreeNode(5);
// Построение ссылок (указателей) между узлами
n1->left = n2;
n1->right = n3;
n2->left = n4;
n2->right = n5;
```
=== "Kotlin"
```kotlin title="binary_tree.kt"
// Инициализация узлов
val n1 = TreeNode(1)
val n2 = TreeNode(2)
val n3 = TreeNode(3)
val n4 = TreeNode(4)
val n5 = TreeNode(5)
// Построение ссылок (указателей) между узлами
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
```
=== "Ruby"
```ruby title="binary_tree.rb"
# Инициализация двоичного дерева
# Инициализация узлов
n1 = TreeNode.new(1)
n2 = TreeNode.new(2)
n3 = TreeNode.new(3)
n4 = TreeNode.new(4)
n5 = TreeNode.new(5)
# Построение ссылок (указателей) между узлами
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
```
??? pythontutor "Визуализация выполнения"
https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%20%28%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D0%B8%29%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
### 2. &nbsp; Вставка и удаление узлов
Как и в связном списке, вставка и удаление узлов в двоичном дереве могут выполняться через изменение указателей. На рисунке 7-3 приведен пример.
![Вставка и удаление узлов в двоичном дереве](binary_tree.assets/binary_tree_add_remove.png){ class="animation-figure" }
<p align="center"> Рисунок 7-3 &nbsp; Вставка и удаление узлов в двоичном дереве </p>
=== "Python"
```python title="binary_tree.py"
# Вставка и удаление узлов
p = TreeNode(0)
# Вставить узел P между n1 -> n2
n1.left = p
p.left = n2
# Удалить узел P
n1.left = n2
```
=== "C++"
```cpp title="binary_tree.cpp"
/* Вставка и удаление узлов */
TreeNode* P = new TreeNode(0);
// Вставить узел P между n1 -> n2
n1->left = P;
P->left = n2;
// Удалить узел P
n1->left = n2;
// Освободить память
delete P;
```
=== "Java"
```java title="binary_tree.java"
TreeNode P = new TreeNode(0);
// Вставить узел P между n1 -> n2
n1.left = P;
P.left = n2;
// Удалить узел P
n1.left = n2;
```
=== "C#"
```csharp title="binary_tree.cs"
/* Вставка и удаление узлов */
TreeNode P = new(0);
// Вставить узел P между n1 -> n2
n1.left = P;
P.left = n2;
// Удалить узел P
n1.left = n2;
```
=== "Go"
```go title="binary_tree.go"
/* Вставка и удаление узлов */
// Вставить узел P между n1 -> n2
p := NewTreeNode(0)
n1.Left = p
p.Left = n2
// Удалить узел P
n1.Left = n2
```
=== "Swift"
```swift title="binary_tree.swift"
let P = TreeNode(x: 0)
// Вставить узел P между n1 -> n2
n1.left = P
P.left = n2
// Удалить узел P
n1.left = n2
```
=== "JS"
```javascript title="binary_tree.js"
/* Вставка и удаление узлов */
let P = new TreeNode(0);
// Вставить узел P между n1 -> n2
n1.left = P;
P.left = n2;
// Удалить узел P
n1.left = n2;
```
=== "TS"
```typescript title="binary_tree.ts"
/* Вставка и удаление узлов */
const P = new TreeNode(0);
// Вставить узел P между n1 -> n2
n1.left = P;
P.left = n2;
// Удалить узел P
n1.left = n2;
```
=== "Dart"
```dart title="binary_tree.dart"
/* Вставка и удаление узлов */
TreeNode P = new TreeNode(0);
// Вставить узел P между n1 -> n2
n1.left = P;
P.left = n2;
// Удалить узел P
n1.left = n2;
```
=== "Rust"
```rust title="binary_tree.rs"
let p = TreeNode::new(0);
// Вставить узел P между n1 -> n2
n1.borrow_mut().left = Some(p.clone());
p.borrow_mut().left = Some(n2.clone());
// Удалить узел p
n1.borrow_mut().left = Some(n2);
```
=== "C"
```c title="binary_tree.c"
/* Вставка и удаление узлов */
TreeNode *P = newTreeNode(0);
// Вставить узел P между n1 -> n2
n1->left = P;
P->left = n2;
// Удалить узел P
n1->left = n2;
// Освободить память
free(P);
```
=== "Kotlin"
```kotlin title="binary_tree.kt"
val P = TreeNode(0)
// Вставить узел P между n1 -> n2
n1.left = P
P.left = n2
// Удалить узел P
n1.left = n2
```
=== "Ruby"
```ruby title="binary_tree.rb"
# Вставка и удаление узлов
_p = TreeNode.new(0)
# Вставить узел _p между n1 -> n2
n1.left = _p
_p.left = n2
# Удалить узел
n1.left = n2
```
??? pythontutor "Визуализация выполнения"
https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%20%28%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D0%B8%29%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D0%B8%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20n1%20-%3E%20n2%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
!!! tip
Обрати внимание: вставка узла может изменить исходную логическую структуру двоичного дерева, а удаление узла обычно означает удаление этого узла вместе со всеми его поддеревьями. Поэтому в двоичном дереве операции вставки и удаления обычно являются частью более крупного набора операций, который и реализует осмысленное действие.
## 7.1.3 &nbsp; Распространенные типы двоичных деревьев
### 1. &nbsp; Идеальное двоичное дерево
Как показано на рисунке 7-4, <u>идеальное двоичное дерево (perfect binary tree)</u> полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна $0$ , а у всех остальных узлов степень равна $2$ ; если высота дерева равна $h$ , то общее число узлов равно $2^{h+1} - 1$ , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления.
!!! tip
Обрати внимание: в китайскоязычном сообществе идеальное двоичное дерево часто называют <u>полностью заполненным двоичным деревом</u>.
![Идеальное двоичное дерево](binary_tree.assets/perfect_binary_tree.png){ class="animation-figure" }
<p align="center"> Рисунок 7-4 &nbsp; Идеальное двоичное дерево </p>
### 2. &nbsp; Полное двоичное дерево
Как показано на рисунке 7-5, <u>полное двоичное дерево (complete binary tree)</u> допускает неполное заполнение только на самом нижнем уровне, причем узлы этого уровня должны непрерывно заполняться слева направо. Обрати внимание: идеальное двоичное дерево тоже является полным двоичным деревом.
![Полное двоичное дерево](binary_tree.assets/complete_binary_tree.png){ class="animation-figure" }
<p align="center"> Рисунок 7-5 &nbsp; Полное двоичное дерево </p>
### 3. &nbsp; Строгое двоичное дерево
Как показано на рисунке 7-6, <u>строгое двоичное дерево (full binary tree)</u> имеет у всех нелистовых узлов ровно двух дочерних узлов.
![Строгое двоичное дерево](binary_tree.assets/full_binary_tree.png){ class="animation-figure" }
<p align="center"> Рисунок 7-6 &nbsp; Строгое двоичное дерево </p>
### 4. &nbsp; Сбалансированное двоичное дерево
Как показано на рисунке 7-7, в <u>сбалансированном двоичном дереве (balanced binary tree)</u> для любого узла абсолютное значение разности высот левого и правого поддеревьев не превышает 1 .
![Сбалансированное двоичное дерево](binary_tree.assets/balanced_binary_tree.png){ class="animation-figure" }
<p align="center"> Рисунок 7-7 &nbsp; Сбалансированное двоичное дерево </p>
## 7.1.4 &nbsp; Вырождение двоичного дерева
На рисунке 7-8 показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем "идеальное двоичное дерево"; когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в "связный список".
- Идеальное двоичное дерево соответствует лучшему случаю и позволяет полностью раскрыть преимущества двоичного дерева с точки зрения "разделяй и властвуй".
- Связный список представляет противоположную крайность: все операции становятся линейными, а временная сложность деградирует до $O(n)$ .
![Лучший и худший случаи структуры двоичного дерева](binary_tree.assets/binary_tree_best_worst_cases.png){ class="animation-figure" }
<p align="center"> Рисунок 7-8 &nbsp; Лучший и худший случаи структуры двоичного дерева </p>
Как показано в таблице 7-1, в лучшем и худшем случаях число листовых узлов, общее число узлов, высота и другие характеристики двоичного дерева достигают максимума или минимума.
<p align="center"> Таблица 7-1 &nbsp; Лучший и худший случаи структуры двоичного дерева </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>

View File

@@ -0,0 +1,940 @@
---
comments: true
---
# 7.2 &nbsp; Обход двоичного дерева
С точки зрения физической структуры дерево представляет собой разновидность структуры данных на основе связей, поэтому его обход выполняется через последовательный доступ к узлам по указателям. Однако дерево является нелинейной структурой данных, а значит, его обход сложнее, чем обход связного списка, и для него требуется использовать поисковые алгоритмы.
К распространенным способам обхода двоичного дерева относятся обход по уровням, прямой обход, симметричный обход и обратный обход.
## 7.2.1 &nbsp; Обход по уровням
Как показано на рисунке 7-9, <u>обход по уровням (level-order traversal)</u> проходит двоичное дерево сверху вниз по уровням и на каждом уровне посещает узлы слева направо.
По своей сути обход по уровням относится к <u>обходу в ширину (breadth-first traversal)</u>, также называемому <u>поиском в ширину (breadth-first search, BFS)</u>; он отражает идею "расширяться слой за слоем наружу".
![Обход двоичного дерева по уровням](binary_tree_traversal.assets/binary_tree_bfs.png){ class="animation-figure" }
<p align="center"> Рисунок 7-9 &nbsp; Обход двоичного дерева по уровням </p>
### 1. &nbsp; Код реализации
Обход в ширину обычно реализуется с помощью "очереди". Очередь подчиняется правилу "первым пришел - первым вышел", а обход в ширину подчиняется правилу "продвигаться по уровням", поэтому стоящая за ними идея согласована. Код реализации приведен ниже:
=== "Python"
```python title="binary_tree_bfs.py"
def level_order(root: TreeNode | None) -> list[int]:
"""Обход в ширину"""
# Инициализировать очередь и добавить корневой узел
queue: deque[TreeNode] = deque()
queue.append(root)
# Инициализировать список для хранения последовательности обхода
res = []
while queue:
node: TreeNode = queue.popleft() # Извлечение из очереди
res.append(node.val) # Сохранить значение узла
if node.left is not None:
queue.append(node.left) # Поместить левый дочерний узел в очередь
if node.right is not None:
queue.append(node.right) # Поместить правый дочерний узел в очередь
return res
```
=== "C++"
```cpp title="binary_tree_bfs.cpp"
/* Обход в ширину */
vector<int> levelOrder(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;
}
```
=== "Java"
```java title="binary_tree_bfs.java"
/* Обход в ширину */
List<Integer> levelOrder(TreeNode root) {
// Инициализировать очередь и добавить корневой узел
Queue<TreeNode> queue = new LinkedList<>();
queue.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#"
```csharp title="binary_tree_bfs.cs"
/* Обход в ширину */
List<int> LevelOrder(TreeNode root) {
// Инициализировать очередь и добавить корневой узел
Queue<TreeNode> queue = new();
queue.Enqueue(root);
// Инициализировать список для хранения последовательности обхода
List<int> list = [];
while (queue.Count != 0) {
TreeNode node = queue.Dequeue(); // Извлечение из очереди
list.Add(node.val!.Value); // Сохранить значение узла
if (node.left != null)
queue.Enqueue(node.left); // Поместить левый дочерний узел в очередь
if (node.right != null)
queue.Enqueue(node.right); // Поместить правый дочерний узел в очередь
}
return list;
}
```
=== "Go"
```go title="binary_tree_bfs.go"
/* Обход в ширину */
func levelOrder(root *TreeNode) []any {
// Инициализировать очередь и добавить корневой узел
queue := list.New()
queue.PushBack(root)
// Инициализировать срез для хранения последовательности обхода
nums := make([]any, 0)
for queue.Len() > 0 {
// Извлечение из очереди
node := queue.Remove(queue.Front()).(*TreeNode)
// Сохранить значение узла
nums = append(nums, node.Val)
if node.Left != nil {
// Поместить левый дочерний узел в очередь
queue.PushBack(node.Left)
}
if node.Right != nil {
// Поместить правый дочерний узел в очередь
queue.PushBack(node.Right)
}
}
return nums
}
```
=== "Swift"
```swift title="binary_tree_bfs.swift"
/* Обход в ширину */
func levelOrder(root: TreeNode) -> [Int] {
// Инициализировать очередь и добавить корневой узел
var queue: [TreeNode] = [root]
// Инициализировать список для хранения последовательности обхода
var list: [Int] = []
while !queue.isEmpty {
let node = queue.removeFirst() // Извлечение из очереди
list.append(node.val) // Сохранить значение узла
if let left = node.left {
queue.append(left) // Поместить левый дочерний узел в очередь
}
if let right = node.right {
queue.append(right) // Поместить правый дочерний узел в очередь
}
}
return list
}
```
=== "JS"
```javascript title="binary_tree_bfs.js"
/* Обход в ширину */
function levelOrder(root) {
// Инициализировать очередь и добавить корневой узел
const queue = [root];
// Инициализировать список для хранения последовательности обхода
const list = [];
while (queue.length) {
let node = queue.shift(); // Извлечение из очереди
list.push(node.val); // Сохранить значение узла
if (node.left) queue.push(node.left); // Поместить левый дочерний узел в очередь
if (node.right) queue.push(node.right); // Поместить правый дочерний узел в очередь
}
return list;
}
```
=== "TS"
```typescript title="binary_tree_bfs.ts"
/* Обход в ширину */
function levelOrder(root: TreeNode | null): number[] {
// Инициализировать очередь и добавить корневой узел
const queue = [root];
// Инициализировать список для хранения последовательности обхода
const list: number[] = [];
while (queue.length) {
let node = queue.shift() as TreeNode; // Извлечение из очереди
list.push(node.val); // Сохранить значение узла
if (node.left) {
queue.push(node.left); // Поместить левый дочерний узел в очередь
}
if (node.right) {
queue.push(node.right); // Поместить правый дочерний узел в очередь
}
}
return list;
}
```
=== "Dart"
```dart title="binary_tree_bfs.dart"
/* Обход в ширину */
List<int> levelOrder(TreeNode? root) {
// Инициализировать очередь и добавить корневой узел
Queue<TreeNode?> queue = Queue();
queue.add(root);
// Инициализировать список для хранения последовательности обхода
List<int> res = [];
while (queue.isNotEmpty) {
TreeNode? node = queue.removeFirst(); // Извлечение из очереди
res.add(node!.val); // Сохранить значение узла
if (node.left != null) queue.add(node.left); // Поместить левый дочерний узел в очередь
if (node.right != null) queue.add(node.right); // Поместить правый дочерний узел в очередь
}
return res;
}
```
=== "Rust"
```rust title="binary_tree_bfs.rs"
/* Обход в ширину */
fn level_order(root: &Rc<RefCell<TreeNode>>) -> Vec<i32> {
// Инициализировать очередь и добавить корневой узел
let mut que = VecDeque::new();
que.push_back(root.clone());
// Инициализировать список для хранения последовательности обхода
let mut vec = Vec::new();
while let Some(node) = que.pop_front() {
// Извлечение из очереди
vec.push(node.borrow().val); // Сохранить значение узла
if let Some(left) = node.borrow().left.as_ref() {
que.push_back(left.clone()); // Поместить левый дочерний узел в очередь
}
if let Some(right) = node.borrow().right.as_ref() {
que.push_back(right.clone()); // Поместить правый дочерний узел в очередь
};
}
vec
}
```
=== "C"
```c title="binary_tree_bfs.c"
/* Обход в ширину */
int *levelOrder(TreeNode *root, int *size) {
/* Вспомогательная очередь */
int front, rear;
int index, *arr;
TreeNode *node;
TreeNode **queue;
/* Вспомогательная очередь */
queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE);
// Указатель очереди
front = 0, rear = 0;
// Добавить корневой узел
queue[rear++] = root;
// Инициализировать список для хранения последовательности обхода
/* Вспомогательный массив */
arr = (int *)malloc(sizeof(int) * MAX_SIZE);
// Указатель на массив
index = 0;
while (front < rear) {
// Извлечение из очереди
node = queue[front++];
// Сохранить значение узла
arr[index++] = node->val;
if (node->left != NULL) {
// Поместить левый дочерний узел в очередь
queue[rear++] = node->left;
}
if (node->right != NULL) {
// Поместить правый дочерний узел в очередь
queue[rear++] = node->right;
}
}
// Обновить значение длины массива
*size = index;
arr = realloc(arr, sizeof(int) * (*size));
// Освободить память вспомогательного массива
free(queue);
return arr;
}
```
=== "Kotlin"
```kotlin title="binary_tree_bfs.kt"
/* Обход в ширину */
fun levelOrder(root: TreeNode?): MutableList<Int> {
// Инициализировать очередь и добавить корневой узел
val queue = LinkedList<TreeNode?>()
queue.add(root)
// Инициализировать список для хранения последовательности обхода
val list = mutableListOf<Int>()
while (queue.isNotEmpty()) {
val 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
}
```
=== "Ruby"
```ruby title="binary_tree_bfs.rb"
=begin
File: binary_tree_bfs.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
# ## Обход в ширину ###
def level_order(root)
# Инициализировать очередь и добавить корневой узел
queue = [root]
# Инициализировать список для хранения последовательности обхода
res = []
while !queue.empty?
node = queue.shift # Извлечение из очереди
res << node.val # Сохранить значение узла
queue << node.left unless node.left.nil? # Поместить левый дочерний узел в очередь
queue << node.right unless node.right.nil? # Поместить правый дочерний узел в очередь
end
res
end
```
??? pythontutor "Визуализация кода"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%D0%B2%20%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83%20%3D%20%27%2C%20res%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=127&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%D0%B2%20%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83%20%3D%20%27%2C%20res%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=127&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div>
### 2. &nbsp; Анализ сложности
- **Временная сложность равна $O(n)$** : все узлы посещаются по одному разу, поэтому требуется $O(n)$ времени, где $n$ - число узлов.
- **Пространственная сложность равна $O(n)$** : в худшем случае, то есть для полной двоичной деревообразной структуры, до достижения самого нижнего уровня в очереди одновременно может находиться до $(n + 1) / 2$ узлов, что требует $O(n)$ памяти.
## 7.2.2 &nbsp; Прямой, симметричный и обратный обходы
Соответственно, прямой, симметричный и обратный обходы относятся к <u>обходу в глубину (depth-first traversal)</u>, также называемому <u>поиском в глубину (depth-first search, DFS)</u>; он отражает идею "сначала идти до конца, затем откатываться и продолжать".
На рисунке 7-10 показан принцип работы обхода двоичного дерева в глубину. **Обход в глубину похож на то, как будто мы обходим всю двоичную структуру по внешнему контуру** , и у каждого узла встречаем три позиции, соответствующие прямому, симметричному и обратному обходам.
![Прямой, симметричный и обратный обходы двоичного дерева поиска](binary_tree_traversal.assets/binary_tree_dfs.png){ class="animation-figure" }
<p align="center"> Рисунок 7-10 &nbsp; Прямой, симметричный и обратный обходы двоичного дерева поиска </p>
### 1. &nbsp; Код реализации
Поиск в глубину обычно реализуется через рекурсию:
=== "Python"
```python title="binary_tree_dfs.py"
def pre_order(root: TreeNode | None):
"""Предварительный обход"""
if root is None:
return
# Порядок обхода: корень -> левое поддерево -> правое поддерево
res.append(root.val)
pre_order(root=root.left)
pre_order(root=root.right)
def in_order(root: TreeNode | None):
"""Симметричный обход"""
if root is None:
return
# Порядок обхода: левое поддерево -> корень -> правое поддерево
in_order(root=root.left)
res.append(root.val)
in_order(root=root.right)
def post_order(root: TreeNode | None):
"""Обратный обход"""
if root is None:
return
# Порядок обхода: левое поддерево -> правое поддерево -> корень
post_order(root=root.left)
post_order(root=root.right)
res.append(root.val)
```
=== "C++"
```cpp title="binary_tree_dfs.cpp"
/* Предварительный обход */
void preOrder(TreeNode *root) {
if (root == nullptr)
return;
// Порядок обхода: корень -> левое поддерево -> правое поддерево
vec.push_back(root->val);
preOrder(root->left);
preOrder(root->right);
}
/* Симметричный обход */
void inOrder(TreeNode *root) {
if (root == nullptr)
return;
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(root->left);
vec.push_back(root->val);
inOrder(root->right);
}
/* Обратный обход */
void postOrder(TreeNode *root) {
if (root == nullptr)
return;
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(root->left);
postOrder(root->right);
vec.push_back(root->val);
}
```
=== "Java"
```java title="binary_tree_dfs.java"
/* Предварительный обход */
void preOrder(TreeNode root) {
if (root == null)
return;
// Порядок обхода: корень -> левое поддерево -> правое поддерево
list.add(root.val);
preOrder(root.left);
preOrder(root.right);
}
/* Симметричный обход */
void inOrder(TreeNode root) {
if (root == null)
return;
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(root.left);
list.add(root.val);
inOrder(root.right);
}
/* Обратный обход */
void postOrder(TreeNode root) {
if (root == null)
return;
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(root.left);
postOrder(root.right);
list.add(root.val);
}
```
=== "C#"
```csharp title="binary_tree_dfs.cs"
/* Предварительный обход */
void PreOrder(TreeNode? root) {
if (root == null) return;
// Порядок обхода: корень -> левое поддерево -> правое поддерево
list.Add(root.val!.Value);
PreOrder(root.left);
PreOrder(root.right);
}
/* Симметричный обход */
void InOrder(TreeNode? root) {
if (root == null) return;
// Порядок обхода: левое поддерево -> корень -> правое поддерево
InOrder(root.left);
list.Add(root.val!.Value);
InOrder(root.right);
}
/* Обратный обход */
void PostOrder(TreeNode? root) {
if (root == null) return;
// Порядок обхода: левое поддерево -> правое поддерево -> корень
PostOrder(root.left);
PostOrder(root.right);
list.Add(root.val!.Value);
}
```
=== "Go"
```go title="binary_tree_dfs.go"
/* Предварительный обход */
func preOrder(node *TreeNode) {
if node == nil {
return
}
// Порядок обхода: корень -> левое поддерево -> правое поддерево
nums = append(nums, node.Val)
preOrder(node.Left)
preOrder(node.Right)
}
/* Симметричный обход */
func inOrder(node *TreeNode) {
if node == nil {
return
}
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(node.Left)
nums = append(nums, node.Val)
inOrder(node.Right)
}
/* Обратный обход */
func postOrder(node *TreeNode) {
if node == nil {
return
}
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(node.Left)
postOrder(node.Right)
nums = append(nums, node.Val)
}
```
=== "Swift"
```swift title="binary_tree_dfs.swift"
/* Предварительный обход */
func preOrder(root: TreeNode?) {
guard let root = root else {
return
}
// Порядок обхода: корень -> левое поддерево -> правое поддерево
list.append(root.val)
preOrder(root: root.left)
preOrder(root: root.right)
}
/* Симметричный обход */
func inOrder(root: TreeNode?) {
guard let root = root else {
return
}
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(root: root.left)
list.append(root.val)
inOrder(root: root.right)
}
/* Обратный обход */
func postOrder(root: TreeNode?) {
guard let root = root else {
return
}
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(root: root.left)
postOrder(root: root.right)
list.append(root.val)
}
```
=== "JS"
```javascript title="binary_tree_dfs.js"
/* Предварительный обход */
function preOrder(root) {
if (root === null) return;
// Порядок обхода: корень -> левое поддерево -> правое поддерево
list.push(root.val);
preOrder(root.left);
preOrder(root.right);
}
/* Симметричный обход */
function inOrder(root) {
if (root === null) return;
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(root.left);
list.push(root.val);
inOrder(root.right);
}
/* Обратный обход */
function postOrder(root) {
if (root === null) return;
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(root.left);
postOrder(root.right);
list.push(root.val);
}
```
=== "TS"
```typescript title="binary_tree_dfs.ts"
/* Предварительный обход */
function preOrder(root: TreeNode | null): void {
if (root === null) {
return;
}
// Порядок обхода: корень -> левое поддерево -> правое поддерево
list.push(root.val);
preOrder(root.left);
preOrder(root.right);
}
/* Симметричный обход */
function inOrder(root: TreeNode | null): void {
if (root === null) {
return;
}
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(root.left);
list.push(root.val);
inOrder(root.right);
}
/* Обратный обход */
function postOrder(root: TreeNode | null): void {
if (root === null) {
return;
}
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(root.left);
postOrder(root.right);
list.push(root.val);
}
```
=== "Dart"
```dart title="binary_tree_dfs.dart"
/* Предварительный обход */
void preOrder(TreeNode? node) {
if (node == null) return;
// Порядок обхода: корень -> левое поддерево -> правое поддерево
list.add(node.val);
preOrder(node.left);
preOrder(node.right);
}
/* Симметричный обход */
void inOrder(TreeNode? node) {
if (node == null) return;
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(node.left);
list.add(node.val);
inOrder(node.right);
}
/* Обратный обход */
void postOrder(TreeNode? node) {
if (node == null) return;
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(node.left);
postOrder(node.right);
list.add(node.val);
}
```
=== "Rust"
```rust title="binary_tree_dfs.rs"
/* Предварительный обход */
fn pre_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut result = vec![];
fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {
if let Some(node) = root {
// Порядок обхода: корень -> левое поддерево -> правое поддерево
let node = node.borrow();
res.push(node.val);
dfs(node.left.as_ref(), res);
dfs(node.right.as_ref(), res);
}
}
dfs(root, &mut result);
result
}
/* Симметричный обход */
fn in_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut result = vec![];
fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {
if let Some(node) = root {
// Порядок обхода: левое поддерево -> корень -> правое поддерево
let node = node.borrow();
dfs(node.left.as_ref(), res);
res.push(node.val);
dfs(node.right.as_ref(), res);
}
}
dfs(root, &mut result);
result
}
/* Обратный обход */
fn post_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut result = vec![];
fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {
if let Some(node) = root {
// Порядок обхода: левое поддерево -> правое поддерево -> корень
let node = node.borrow();
dfs(node.left.as_ref(), res);
dfs(node.right.as_ref(), res);
res.push(node.val);
}
}
dfs(root, &mut result);
result
}
```
=== "C"
```c title="binary_tree_dfs.c"
/* Предварительный обход */
void preOrder(TreeNode *root, int *size) {
if (root == NULL)
return;
// Порядок обхода: корень -> левое поддерево -> правое поддерево
arr[(*size)++] = root->val;
preOrder(root->left, size);
preOrder(root->right, size);
}
/* Симметричный обход */
void inOrder(TreeNode *root, int *size) {
if (root == NULL)
return;
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(root->left, size);
arr[(*size)++] = root->val;
inOrder(root->right, size);
}
/* Обратный обход */
void postOrder(TreeNode *root, int *size) {
if (root == NULL)
return;
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(root->left, size);
postOrder(root->right, size);
arr[(*size)++] = root->val;
}
```
=== "Kotlin"
```kotlin title="binary_tree_dfs.kt"
/* Предварительный обход */
fun preOrder(root: TreeNode?) {
if (root == null) return
// Порядок обхода: корень -> левое поддерево -> правое поддерево
list.add(root._val)
preOrder(root.left)
preOrder(root.right)
}
/* Симметричный обход */
fun inOrder(root: TreeNode?) {
if (root == null) return
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(root.left)
list.add(root._val)
inOrder(root.right)
}
/* Обратный обход */
fun postOrder(root: TreeNode?) {
if (root == null) return
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(root.left)
postOrder(root.right)
list.add(root._val)
}
```
=== "Ruby"
```ruby title="binary_tree_dfs.rb"
=begin
File: binary_tree_dfs.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
# ## Предварительный обход ###
def pre_order(root)
return if root.nil?
# Порядок обхода: корень -> левое поддерево -> правое поддерево
$res << root.val
pre_order(root.left)
pre_order(root.right)
end
=begin
File: binary_tree_dfs.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
# ## Предварительный обход ###
def pre_order(root)
return if root.nil?
# Порядок обхода: корень -> левое поддерево -> правое поддерево
$res << root.val
pre_order(root.left)
pre_order(root.right)
end
# ## Симметричный обход ###
def in_order(root)
return if root.nil?
# Порядок обхода: левое поддерево -> корень -> правое поддерево
in_order(root.left)
$res << root.val
in_order(root.right)
end
=begin
File: binary_tree_dfs.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
# ## Предварительный обход ###
def pre_order(root)
return if root.nil?
# Порядок обхода: корень -> левое поддерево -> правое поддерево
$res << root.val
pre_order(root.left)
pre_order(root.right)
end
# ## Симметричный обход ###
def in_order(root)
return if root.nil?
# Порядок обхода: левое поддерево -> корень -> правое поддерево
in_order(root.left)
$res << root.val
in_order(root.right)
end
# ## Обратный обход ###
def post_order(root)
return if root.nil?
# Порядок обхода: левое поддерево -> правое поддерево -> корень
post_order(root.left)
post_order(root.right)
$res << root.val
end
```
??? pythontutor "Визуализация кода"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BF%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=129&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BF%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=129&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div>
!!! tip
Поиск в глубину можно реализовать и итеративно; заинтересованные читатели могут изучить это самостоятельно.
На рисунках ниже показан рекурсивный процесс прямого обхода двоичного дерева. Его можно разделить на две противоположные части: "вход в рекурсию" и "возврат".
1. "Вход в рекурсию" означает запуск нового вызова функции; в этом процессе программа переходит к следующему узлу.
2. "Возврат" означает завершение вызова функции и возврат назад, то есть текущий узел уже полностью обработан.
=== "<1>"
![Рекурсивный процесс прямого обхода](binary_tree_traversal.assets/preorder_step1.png){ class="animation-figure" }
=== "<2>"
![preorder_step2](binary_tree_traversal.assets/preorder_step2.png){ class="animation-figure" }
=== "<3>"
![preorder_step3](binary_tree_traversal.assets/preorder_step3.png){ class="animation-figure" }
=== "<4>"
![preorder_step4](binary_tree_traversal.assets/preorder_step4.png){ class="animation-figure" }
=== "<5>"
![preorder_step5](binary_tree_traversal.assets/preorder_step5.png){ class="animation-figure" }
=== "<6>"
![preorder_step6](binary_tree_traversal.assets/preorder_step6.png){ class="animation-figure" }
=== "<7>"
![preorder_step7](binary_tree_traversal.assets/preorder_step7.png){ class="animation-figure" }
=== "<8>"
![preorder_step8](binary_tree_traversal.assets/preorder_step8.png){ class="animation-figure" }
=== "<9>"
![preorder_step9](binary_tree_traversal.assets/preorder_step9.png){ class="animation-figure" }
=== "<10>"
![preorder_step10](binary_tree_traversal.assets/preorder_step10.png){ class="animation-figure" }
=== "<11>"
![preorder_step11](binary_tree_traversal.assets/preorder_step11.png){ class="animation-figure" }
<p align="center"> Рисунок 7-11 &nbsp; Рекурсивный процесс прямого обхода </p>
### 2. &nbsp; Анализ сложности
- **Временная сложность равна $O(n)$** : все узлы посещаются по одному разу, поэтому требуется $O(n)$ времени.
- **Пространственная сложность равна $O(n)$** : в худшем случае, когда дерево вырождается в связный список, глубина рекурсии достигает $n$ , и система тратит $O(n)$ памяти на стек вызовов.

View File

@@ -0,0 +1,23 @@
---
comments: true
icon: material/graph-outline
---
# Глава 7. &nbsp; Деревья
![Деревья](../assets/covers/chapter_tree.jpg){ class="cover-image" }
!!! abstract
Высокое дерево полно жизни: мощные корни, густая крона и раскидистые ветви.
Оно наглядно показывает нам форму данных, построенную на принципе "разделяй и властвуй".
## Содержание главы
- [7.1 &nbsp; Двоичное дерево](binary_tree.md)
- [7.2 &nbsp; Обход двоичного дерева](binary_tree_traversal.md)
- [7.3 &nbsp; Представление дерева массивом](array_representation_of_tree.md)
- [7.4 &nbsp; Двоичное дерево поиска](binary_search_tree.md)
- [7.5 &nbsp; AVL-дерево *](avl_tree.md)
- [7.6 &nbsp; Резюме](summary.md)

View File

@@ -0,0 +1,58 @@
---
comments: true
---
# 7.6 &nbsp; Краткие итоги
### 1. &nbsp; Основные моменты
- Двоичное дерево - это нелинейная структура данных, отражающая логику "разделения надвое". Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам.
- Для любого узла двоичного дерева дерево, образованное его левым (правым) дочерним узлом и всеми нижележащими узлами, называется левым (правым) поддеревом этого узла.
- К связанным с двоичным деревом терминам относятся корневой узел, листовой узел, уровень, степень, ребро, высота, глубина и так далее.
- Инициализация двоичного дерева, вставка узлов и удаление узлов похожи по способу реализации на операции со связным списком.
- К распространенным видам двоичного дерева относятся идеальное двоичное дерево, полное двоичное дерево, строгое двоичное дерево и сбалансированное двоичное дерево. Идеальное двоичное дерево - наиболее желательное состояние, а связный список - худший случай после вырождения.
- Двоичное дерево можно представить массивом: значения узлов и пустые позиции располагаются в порядке обхода по уровням, а связи между родителем и детьми реализуются через отображение индексов.
- Обход двоичного дерева по уровням является методом поиска в ширину; он отражает идею "расширяться слой за слоем наружу" и обычно реализуется через очередь.
- Прямой, симметричный и обратный обходы относятся к поиску в глубину; они отражают идею "сначала дойти до конца, затем откатиться и продолжить" и обычно реализуются рекурсивно.
- Двоичное дерево поиска - это эффективная структура данных для поиска элементов; его поиск, вставка и удаление имеют временную сложность $O(\log n)$ . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до $O(n)$ .
- AVL-дерево, также называемое сбалансированным двоичным деревом поиска, с помощью вращений гарантирует, что после постоянных вставок и удалений узлов дерево остается сбалансированным.
- Вращения AVL-дерева включают правое вращение, левое вращение, сначала правое затем левое и сначала левое затем правое. После вставки или удаления узла AVL-дерево выполняет вращения снизу вверх, чтобы снова восстановить баланс.
### 2. &nbsp; Q & A
**Q**: Для двоичного дерева, состоящего из одного узла, высота дерева и глубина корня обе равны $0$ ?
Да, потому что высота и глубина обычно определяются как "число пройденных ребер".
**Q**: Вставка и удаление в двоичном дереве обычно выполняются в составе набора операций. Что именно означает этот "набор операций"? Можно ли понимать это как освобождение ресурсов у дочерних узлов ресурса?
Возьмем в качестве примера двоичное дерево поиска: операция удаления узла делится на три случая, и каждый из этих случаев требует нескольких последовательных шагов работы с узлами.
**Q**: Почему у DFS для двоичного дерева есть три порядка: прямой, симметричный и обратный? Для чего они нужны?
Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы - это три способа обхода двоичного дерева, с помощью которых можно получить результаты в определенном порядке. Например, в двоичном дереве поиска, где соблюдается отношение `значение левого дочернего узла < значение корня < значение правого дочернего узла` , если обходить дерево с приоритетом "лево $\rightarrow$ корень $\rightarrow$ право", то получится упорядоченная последовательность узлов.
**Q**: Правое вращение работает с отношениями между `node` , `child` и `grand_child` . А связь между `node` и его исходным родителем разве не нужно поддерживать? После правого вращения она ведь не оборвется?
На это нужно смотреть с точки зрения рекурсии. В правое вращение `right_rotate(root)` передается корень поддерева, а затем через `return child` возвращается корень этого поддерева уже после вращения. Соединение между новым корнем поддерева и его родителем восстанавливается после возврата функции и не входит в обязанности самой операции правого вращения.
**Q**: В C++ функции делятся на `private` и `public` . Какая логика стоит за этим? Почему `height()` и `updateHeight()` помещают в разные области видимости?
Главный критерий - область использования метода. Если метод нужен только внутри класса, его следует проектировать как `private` . Например, самостоятельный вызов `updateHeight()` пользователем не имеет смысла: это лишь один из шагов внутри вставки или удаления. А `height()` используется для чтения высоты узла, подобно `vector.size()` , поэтому его разумно делать `public` .
**Q**: Как построить двоичное дерево поиска из набора входных данных? Важен ли выбор корневого узла?
Да, важен. Способ построения дерева уже показан в методе `build_tree()` в коде двоичного дерева поиска. Что касается выбора корня, обычно входные данные сортируют, берут средний элемент как корень, а затем рекурсивно строят левое и правое поддеревья. Это позволяет в наибольшей степени сохранить баланс дерева.
**Q**: Нужно ли в Java всегда использовать `equals()` для сравнения строк?
В Java для базовых типов `==` используется, чтобы сравнивать, равны ли значения двух переменных. Для ссылочных типов логика у этих двух способов уже разная.
- `==` : сравнивает, ссылаются ли две переменные на один и тот же объект, то есть совпадает ли их адрес в памяти.
- `equals()`: сравнивает, равны ли значения двух объектов.
Поэтому если нужно сравнить значения, то следует использовать `equals()` . Но строки, инициализированные как `String a = "hi"; String b = "hi";` , хранятся в строковом пуле констант и указывают на один и тот же объект, поэтому в таком случае `a == b` тоже может дать истинный результат при сравнении содержимого.
**Q**: До достижения самого нижнего уровня при обходе в ширину число узлов в очереди равно $2^h$ ?
Да. Например, для полного двоичного дерева высоты $h = 2$ общее число узлов равно $n = 7$ , а число узлов на нижнем уровне равно $4 = 2^h = (n + 1) / 2$ .