fix Ru translation (#1894)

* docs(ru): replace prose quotes with guillemets

* docs(ru): replace prose semicolons with periods

* docs(ru): align animation title forms

* docs(ru): align figure and table references
This commit is contained in:
Yudong Jin
2026-04-14 18:10:12 +08:00
committed by GitHub
parent 1d12e2def5
commit 22b3b568ef
94 changed files with 565 additions and 570 deletions
@@ -16,7 +16,7 @@
## Представление произвольного двоичного дерева
Идеальное двоичное дерево - лишь частный случай; в обычной двоичной структуре на промежуточных уровнях часто существует множество `None` . Поскольку последовательность обхода по уровням не содержит этих `None` , мы не можем по одной лишь этой последовательности определить их количество и расположение. **Это означает, что одному и тому же обходу по уровням может соответствовать сразу несколько различных структур двоичного дерева**.
Идеальное двоичное дерево - лишь частный случай. В обычной двоичной структуре на промежуточных уровнях часто существует множество `None` . Поскольку последовательность обхода по уровням не содержит этих `None` , мы не можем по одной лишь этой последовательности определить их количество и расположение. **Это означает, что одному и тому же обходу по уровням может соответствовать сразу несколько различных структур двоичного дерева**.
Как показано на рисунке ниже, для неполной двоичной структуры описанный выше способ представления массивом уже перестает работать.
+15 -15
View File
@@ -1,6 +1,6 @@
# AVL-дерево *
В разделе "Двоичное дерево поиска" мы упоминали, что после многократных операций вставки и удаления узлов двоичное дерево поиска может выродиться в связный список. В таком случае временная сложность всех операций ухудшается с $O(\log n)$ до $O(n)$ .
В разделе «Двоичное дерево поиска» мы упоминали, что после многократных операций вставки и удаления узлов двоичное дерево поиска может выродиться в связный список. В таком случае временная сложность всех операций ухудшается с $O(\log n)$ до $O(n)$ .
Как показано на рисунке ниже, после двух операций удаления узлов это двоичное дерево поиска вырождается в связный список.
@@ -10,7 +10,7 @@
![Деградация AVL-дерева после вставки узлов](avl_tree.assets/avltree_degradation_from_inserting_node.png)
В 1962 году Г. М. Adelson-Velsky и Е. М. Landis в статье "An algorithm for the organization of information" предложили <u>AVL-дерево</u>. В статье подробно описан набор операций, гарантирующий, что при непрерывном добавлении и удалении узлов AVL-дерево не вырождается, благодаря чему временная сложность различных операций сохраняется на уровне $O(\log n)$ . Иначе говоря, в сценариях, где часто выполняются вставка, удаление, поиск и изменение, AVL-дерево всегда поддерживает эффективную работу с данными и потому имеет высокую практическую ценность.
В 1962 году Г. М. Adelson-Velsky и Е. М. Landis в статье «An algorithm for the organization of information» предложили <u>AVL-дерево</u>. В статье подробно описан набор операций, гарантирующий, что при непрерывном добавлении и удалении узлов AVL-дерево не вырождается, благодаря чему временная сложность различных операций сохраняется на уровне $O(\log n)$ . Иначе говоря, в сценариях, где часто выполняются вставка, удаление, поиск и изменение, AVL-дерево всегда поддерживает эффективную работу с данными и потому имеет высокую практическую ценность.
## Распространенные термины AVL-дерева
@@ -228,7 +228,7 @@ AVL-дерево одновременно является и двоичным
end
```
"Высота узла" означает расстояние от этого узла до самого удаленного листового узла, то есть число пройденных "ребер". Особенно важно помнить, что высота листового узла равна $0$ , а высота пустого узла равна $-1$ . Мы создадим две вспомогательные функции: одну для получения высоты узла, другую для ее обновления:
«Высота узла» означает расстояние от этого узла до самого удаленного листового узла, то есть число пройденных «ребер». Особенно важно помнить, что высота листового узла равна $0$ , а высота пустого узла равна $-1$ . Мы создадим две вспомогательные функции: одну для получения высоты узла, другую для ее обновления:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{update_height}
@@ -236,7 +236,7 @@ AVL-дерево одновременно является и двоичным
### Баланс-фактор узла
<u>Баланс-фактор (balance factor)</u> узла определяется как высота левого поддерева минус высота правого поддерева; при этом баланс-фактор пустого узла считается равным $0$ . Мы также инкапсулируем получение баланс-фактора в отдельную функцию, чтобы потом было удобнее ее использовать:
<u>Баланс-фактор (balance factor)</u> узла определяется как высота левого поддерева минус высота правого поддерева. При этом баланс-фактор пустого узла считается равным $0$ . Мы также инкапсулируем получение баланс-фактора в отдельную функцию, чтобы потом было удобнее ее использовать:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor}
@@ -244,17 +244,17 @@ AVL-дерево одновременно является и двоичным
!!! tip
Пусть баланс-фактор равен $f$ ; тогда для любого узла AVL-дерева выполняется $-1 \le f \le 1$ .
Пусть баланс-фактор равен $f$. Тогда для любого узла AVL-дерева выполняется $-1 \le f \le 1$ .
## Вращения AVL-дерева
Особенность AVL-дерева заключается в операции "вращения", которая позволяет заново сбалансировать разбалансированный узел, не нарушая последовательность симметричного обхода двоичного дерева. Иначе говоря, **операция вращения одновременно сохраняет свойство "двоичного дерева поиска" и возвращает дерево в состояние "сбалансированного двоичного дерева"**.
Особенность AVL-дерева заключается в операции «вращения», которая позволяет заново сбалансировать разбалансированный узел, не нарушая последовательность симметричного обхода двоичного дерева. Иначе говоря, **операция вращения одновременно сохраняет свойство «двоичного дерева поиска» и возвращает дерево в состояние «сбалансированного двоичного дерева»**.
Узлы, для которых абсолютное значение баланс-фактора больше $1$ , мы называем "разбалансированными узлами". В зависимости от вида разбаланса вращения делятся на четыре типа: правое вращение, левое вращение, сначала левое затем правое, и сначала правое затем левое. Ниже разберем их подробно.
Узлы, для которых абсолютное значение баланс-фактора больше $1$ , мы называем «разбалансированными узлами». В зависимости от вида разбаланса вращения делятся на четыре типа: правое вращение, левое вращение, сначала левое затем правое, и сначала правое затем левое. Ниже разберем их подробно.
### Правое вращение
Как показано на рисунках ниже, под узлом указан его баланс-фактор. Если двигаться снизу вверх, то первым разбалансированным узлом в двоичном дереве будет "узел 3". Рассмотрим поддерево с этим узлом в качестве корня, обозначим данный узел как `node` , его левого дочернего узла как `child` и выполним "правое вращение". После завершения правого вращения поддерево снова станет сбалансированным и при этом сохранит свойство двоичного дерева поиска.
Как показано на рисунке ниже, под узлом указан его баланс-фактор. Если двигаться снизу вверх, то первым разбалансированным узлом в двоичном дереве будет «узел 3». Рассмотрим поддерево с этим узлом в качестве корня, обозначим данный узел как `node` , его левого дочернего узла как `child` и выполним «правое вращение». После завершения правого вращения поддерево снова станет сбалансированным и при этом сохранит свойство двоичного дерева поиска.
=== "<1>"
![Шаги правого вращения](avl_tree.assets/avltree_right_rotate_step1.png)
@@ -272,7 +272,7 @@ AVL-дерево одновременно является и двоичным
![Правое вращение при наличии grand_child](avl_tree.assets/avltree_right_rotate_with_grandchild.png)
"Поворот вправо" - это лишь образное описание; в реальности он реализуется через изменение указателей узлов. Код приведен ниже:
«Поворот вправо» - это лишь образное описание. В реальности он реализуется через изменение указателей узлов. Код приведен ниже:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate}
@@ -280,11 +280,11 @@ AVL-дерево одновременно является и двоичным
### Левое вращение
Соответственно, если рассмотреть "зеркальную" версию приведенного выше разбалансированного двоичного дерева, то понадобится выполнить "левое вращение", показанное на рисунке ниже.
Соответственно, если рассмотреть «зеркальную» версию приведенного выше разбалансированного двоичного дерева, то понадобится выполнить «левое вращение», показанное на рисунке ниже.
![Левое вращение](avl_tree.assets/avltree_left_rotate.png)
По той же причине, когда у узла `child` есть левый дочерний узел, который обозначим как `grand_child` , в левое вращение также требуется добавить шаг: сделать `grand_child` правым дочерним узлом `node` .
Аналогичная ситуация показана на рисунке ниже. Если у узла `child` есть левый дочерний узел, который обозначим как `grand_child` , то в левое вращение также требуется добавить шаг: сделать `grand_child` правым дочерним узлом `node` .
![Левое вращение при наличии grand_child](avl_tree.assets/avltree_left_rotate_with_grandchild.png)
@@ -296,23 +296,23 @@ AVL-дерево одновременно является и двоичным
### Сначала левое, затем правое вращение
Для разбалансированного узла 3 на рисунке ниже ни одно лишь левое вращение, ни одно лишь правое вращение не способны вернуть поддерево в баланс. В этом случае нужно сначала выполнить "левое вращение" для `child` , а затем выполнить "правое вращение" для `node` .
Для разбалансированного узла 3 на рисунке ниже ни одно лишь левое вращение, ни одно лишь правое вращение не способны вернуть поддерево в баланс. В этом случае нужно сначала выполнить «левое вращение» для `child` , а затем выполнить «правое вращение» для `node` .
![Сначала левое, затем правое вращение](avl_tree.assets/avltree_left_right_rotate.png)
### Сначала правое, затем левое вращение
Как показано на рисунке ниже, для зеркальной ситуации предыдущего разбалансированного двоичного дерева нужно сначала выполнить "правое вращение" для `child` , а затем "левое вращение" для `node` .
Как показано на рисунке ниже, для зеркальной ситуации предыдущего разбалансированного двоичного дерева нужно сначала выполнить «правое вращение» для `child` , а затем «левое вращение» для `node` .
![Сначала правое, затем левое вращение](avl_tree.assets/avltree_right_left_rotate.png)
### Выбор вращения
Четыре вида разбаланса, показанные на рисунке ниже, по одному соответствуют рассмотренным выше случаям; для них соответственно требуются правое вращение, сначала левое затем правое, сначала правое затем левое и левое вращение.
Четыре вида разбаланса, показанные на рисунке ниже, по одному соответствуют рассмотренным выше случаям. Для них соответственно требуются правое вращение, сначала левое затем правое, сначала правое затем левое и левое вращение.
![Четыре случая вращений AVL-дерева](avl_tree.assets/avltree_rotation_cases.png)
Как показано в таблице ниже, мы определяем, какому из этих четырех случаев соответствует разбалансированный узел, по знаку баланс-фактора самого разбалансированного узла и по знаку баланс-фактора дочернего узла на более высокой стороне.
Как показано в таблице ниже, мы определяем, какому из случаев на рисунке выше соответствует разбалансированный узел, по знаку баланс-фактора самого разбалансированного узла и по знаку баланс-фактора дочернего узла на более высокой стороне.
<p align="center"> Таблица <id> &nbsp; Условия выбора для четырех случаев вращений </p>
+9 -9
View File
@@ -13,7 +13,7 @@
### Поиск узла
Для заданного целевого значения узла `num` можно выполнить поиск, опираясь на свойства двоичного дерева поиска. Как показано на рисунках ниже, мы объявляем узел `cur` , стартуем от корня дерева `root` и циклически сравниваем значения `cur.val` и `num` .
Для заданного целевого значения узла `num` можно выполнить поиск, опираясь на свойства двоичного дерева поиска. Как показано на рисунке ниже, мы объявляем узел `cur` , стартуем от корня дерева `root` и циклически сравниваем значения `cur.val` и `num` .
- Если `cur.val < num` , это означает, что целевой узел находится в правом поддереве `cur` , поэтому выполняем `cur = cur.right` .
- Если `cur.val > num` , это означает, что целевой узел находится в левом поддереве `cur` , поэтому выполняем `cur = cur.left` .
@@ -39,7 +39,7 @@
### Вставка узла
Пусть дан элемент `num` , который нужно вставить. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево < корень < правое поддерево", процесс вставки выглядит следующим образом.
Пусть дан элемент `num` , который нужно вставить. Чтобы сохранить свойство двоичного дерева поиска «левое поддерево < корень < правое поддерево», процесс вставки показан на рисунке ниже.
1. **Найти позицию для вставки**: как и в операции поиска, начиная от корня, мы циклически спускаемся вниз в зависимости от соотношения между текущим значением узла и `num` , пока не выйдем за листовой узел (то есть не дойдем до `None` ).
2. **Вставить узел в найденную позицию**: инициализировать узел `num` и поставить его на место этого `None` .
@@ -59,7 +59,7 @@
### Удаление узла
Сначала нужно найти в двоичном дереве целевой узел, а затем удалить его. Как и при вставке, после удаления необходимо сохранить свойство двоичного дерева поиска: "левое поддерево < корень < правое поддерево". Поэтому в зависимости от числа дочерних узлов у удаляемого узла, то есть для случаев со степенью 0, 1 и 2, выполняются разные операции удаления.
Сначала нужно найти в двоичном дереве целевой узел, а затем удалить его. Как и при вставке, после удаления необходимо сохранить свойство двоичного дерева поиска: «левое поддерево < корень < правое поддерево». Поэтому в зависимости от числа дочерних узлов у удаляемого узла, то есть для случаев со степенью 0, 1 и 2, выполняются разные операции удаления.
Как показано на рисунке ниже, когда степень удаляемого узла равна $0$ , это значит, что узел является листом и может быть удален напрямую.
@@ -69,11 +69,11 @@
![Удаление узла в двоичном дереве поиска (степень 1)](binary_search_tree.assets/bst_remove_case2.png)
Когда степень удаляемого узла равна $2$ , мы уже не можем удалить его напрямую и должны использовать для замены другой узел. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево $<$ корень $<$ правое поддерево", **этим узлом может быть минимальный узел правого поддерева или максимальный узел левого поддерева**.
Когда степень удаляемого узла равна $2$ , мы уже не можем удалить его напрямую и должны использовать для замены другой узел. Чтобы сохранить свойство двоичного дерева поиска «левое поддерево $<$ корень $<$ правое поддерево», **этим узлом может быть минимальный узел правого поддерева или максимальный узел левого поддерева**.
Предположим, мы выбираем минимальный узел правого поддерева, то есть следующий узел в симметричном обходе. Тогда процесс удаления выглядит так.
Предположим, мы выбираем минимальный узел правого поддерева, то есть следующий узел в симметричном обходе. Тогда процесс удаления показан на рисунке ниже.
1. Найти следующий узел в "последовательности симметричного обхода" для удаляемого узла и обозначить его как `tmp` .
1. Найти следующий узел в «последовательности симметричного обхода» для удаляемого узла и обозначить его как `tmp` .
2. Значением `tmp` перезаписать значение удаляемого узла, а затем рекурсивно удалить узел `tmp` из дерева.
=== "<1>"
@@ -96,7 +96,7 @@
### Упорядоченность симметричного обхода
Как показано на рисунке ниже, симметричный обход двоичного дерева следует порядку "лево $\rightarrow$ корень $\rightarrow$ право", а двоичное дерево поиска удовлетворяет соотношению "левый дочерний узел $<$ корень $<$ правый дочерний узел".
Как показано на рисунке ниже, симметричный обход двоичного дерева следует порядку «лево $\rightarrow$ корень $\rightarrow$ право», а двоичное дерево поиска удовлетворяет соотношению «левый дочерний узел $<$ корень $<$ правый дочерний узел».
Это означает, что при симметричном обходе двоичного дерева поиска мы всегда сначала будем посещать следующий минимальный узел, и отсюда получается важное свойство: **последовательность симметричного обхода двоичного дерева поиска является возрастающей**.
@@ -106,7 +106,7 @@
## Эффективность двоичного дерева поиска
Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок и обеспечивает стабильную высокую производительность. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.
Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Как видно по данным в таблице ниже, временная сложность операций двоичного дерева поиска имеет логарифмический порядок и обеспечивает стабильную высокую производительность. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.
<p align="center"> Таблица <id> &nbsp; Сравнение эффективности массива и дерева поиска </p>
@@ -116,7 +116,7 @@
| Вставка элемента | $O(1)$ | $O(\log n)$ |
| Удаление элемента | $O(n)$ | $O(\log n)$ |
В идеальном случае двоичное дерево поиска является "сбалансированным", и тогда любой узел можно найти за $\log n$ итераций.
В идеальном случае двоичное дерево поиска является «сбалансированным», и тогда любой узел можно найти за $\log n$ итераций.
Однако если в двоичное дерево поиска непрерывно вставлять и удалять узлы, оно может выродиться в связный список, как показано на рисунке ниже. Тогда временная сложность различных операций тоже вырождается до $O(n)$ .
+9 -9
View File
@@ -1,6 +1,6 @@
# Двоичное дерево
<u>Двоичное дерево (binary tree)</u> - это нелинейная структура данных, представляющая отношения между "предками" и "потомками" и отражающая логику "разделяй и властвуй". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.
<u>Двоичное дерево (binary tree)</u> - это нелинейная структура данных, представляющая отношения между «предками» и «потомками» и отражающая логику «разделяй и властвуй». Подобно связному списку, базовой единицей двоичного дерева является узел. Каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.
=== "Python"
@@ -201,9 +201,9 @@
end
```
Каждый узел имеет две ссылки (указателя), которые соответственно указывают на <u>левого дочернего узла (left-child node)</u> и <u>правого дочернего узла (right-child node)</u>; данный узел называется <u>родительским узлом (parent node)</u> для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется <u>левым поддеревом (left subtree)</u> этого узла; аналогично определяется <u>правое поддерево (right subtree)</u>.
Каждый узел имеет две ссылки (указателя), которые соответственно указывают на <u>левого дочернего узла (left-child node)</u> и <u>правого дочернего узла (right-child node)</u>. Данный узел называется <u>родительским узлом (parent node)</u> для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется <u>левым поддеревом (left subtree)</u> этого узла. Аналогично определяется <u>правое поддерево (right subtree)</u>.
**Узлы, не имеющие дочерних узлов, называют листьями, а все остальные узлы содержат дочерние узлы и непустые поддеревья**. Как показано на рисунке ниже, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него".
**Узлы, не имеющие дочерних узлов, называют листьями, а все остальные узлы содержат дочерние узлы и непустые поддеревья**. Как показано на рисунке ниже, если рассматривать «узел 2» как родительский, то его левым и правым дочерними узлами будут «узел 4» и «узел 5». Левое поддерево - это «узел 4 и дерево ниже него», а правое поддерево - это «узел 5 и дерево ниже него».
![Родительский узел, дочерние узлы и поддеревья](binary_tree.assets/binary_tree_definition.png)
@@ -212,9 +212,9 @@
Распространенные термины двоичного дерева показаны на рисунке ниже.
- <u>Корневой узел (root node)</u>: узел, расположенный на верхнем уровне двоичного дерева и не имеющий родительского узла.
- <u>Листовой узел (leaf node)</u>: узел без дочерних узлов; оба его указателя направлены на `None` .
- <u>Листовой узел (leaf node)</u>: узел без дочерних узлов. Оба его указателя направлены на `None` .
- <u>Ребро (edge)</u>: отрезок, соединяющий два узла, то есть ссылка (указатель) между узлами.
- <u>Уровень (level)</u> узла: увеличивается сверху вниз; уровень корневого узла равен 1 .
- <u>Уровень (level)</u> узла: увеличивается сверху вниз. Уровень корневого узла равен 1 .
- <u>Степень (degree)</u> узла: число дочерних узлов данного узла. В двоичном дереве возможны степени 0, 1, 2 .
- <u>Высота (height)</u> двоичного дерева: число ребер от корневого узла до самого удаленного листового узла.
- <u>Глубина (depth)</u> узла: число ребер от корневого узла до данного узла.
@@ -224,7 +224,7 @@
!!! tip
Обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 .
Обычно под «высотой» и «глубиной» понимают «число пройденных ребер», но в некоторых задачах или учебниках их могут определять как «число пройденных узлов». В таком случае и высоту, и глубину нужно увеличить на 1 .
## Базовые операции двоичного дерева
@@ -627,7 +627,7 @@
### Идеальное двоичное дерево
Как показано на рисунке ниже, <u>идеальное двоичное дерево (perfect binary tree)</u> полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна $0$ , а у всех остальных узлов степень равна $2$ ; если высота дерева равна $h$ , то общее число узлов равно $2^{h+1} - 1$ , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления.
Как показано на рисунке ниже, <u>идеальное двоичное дерево (perfect binary tree)</u> полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна $0$ , а у всех остальных узлов степень равна $2$. Если высота дерева равна $h$ , то общее число узлов равно $2^{h+1} - 1$ , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления.
!!! tip
@@ -655,9 +655,9 @@
## Вырождение двоичного дерева
На рисунке ниже показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем "идеальное двоичное дерево"; когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в "связный список".
На рисунке ниже показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем «идеальное двоичное дерево». Когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в «связный список».
- Идеальное двоичное дерево соответствует лучшему случаю и позволяет в полной мере раскрыть преимущества подхода "разделяй и властвуй".
- Идеальное двоичное дерево соответствует лучшему случаю и позволяет в полной мере раскрыть преимущества подхода «разделяй и властвуй».
- Связный список представляет противоположную крайность: все операции становятся линейными, а временная сложность деградирует до $O(n)$ .
![Лучший и худший случаи структуры двоичного дерева](binary_tree.assets/binary_tree_best_worst_cases.png)
@@ -8,13 +8,13 @@
Как показано на рисунке ниже, <u>обход по уровням (level-order traversal)</u> проходит двоичное дерево сверху вниз по уровням и на каждом уровне посещает узлы слева направо.
По своей сути обход по уровням относится к <u>обходу в ширину (breadth-first traversal)</u>, также называемому <u>поиском в ширину (breadth-first search, BFS)</u>; он отражает идею "расширяться от центра к периферии слой за слоем".
По своей сути обход по уровням относится к <u>обходу в ширину (breadth-first traversal)</u>, также называемому <u>поиском в ширину (breadth-first search, BFS)</u>. Он отражает идею «расширяться от центра к периферии слой за слоем».
![Обход двоичного дерева по уровням](binary_tree_traversal.assets/binary_tree_bfs.png)
### Код реализации
Обход в ширину обычно реализуется с помощью "очереди". Очередь подчиняется правилу "первым пришел - первым вышел", а обход в ширину подчиняется правилу "продвигаться по уровням", поэтому стоящая за ними идея согласована. Код реализации приведен ниже:
Обход в ширину обычно реализуется с помощью «очереди». Очередь подчиняется правилу «первым пришел - первым вышел», а обход в ширину подчиняется правилу «продвигаться по уровням», поэтому стоящая за ними идея согласована. Код реализации приведен ниже:
```src
[file]{binary_tree_bfs}-[class]{}-[func]{level_order}
@@ -27,7 +27,7 @@
## Прямой, симметричный и обратный обходы
Соответственно, прямой, симметричный и обратный обходы относятся к <u>обходу в глубину (depth-first traversal)</u>, также называемому <u>поиском в глубину (depth-first search, DFS)</u>; он отражает идею "сначала идти до конца, затем возвращаться и продолжать".
Соответственно, прямой, симметричный и обратный обходы относятся к <u>обходу в глубину (depth-first traversal)</u>, также называемому <u>поиском в глубину (depth-first search, DFS)</u>. Он отражает идею «сначала идти до конца, затем возвращаться и продолжать».
На рисунке ниже показан принцип работы обхода двоичного дерева в глубину. **Обход в глубину можно представить как обход всей двоичной структуры по внешнему контуру** , и у каждого узла встречаются три позиции, соответствующие прямому, симметричному и обратному обходам.
@@ -43,12 +43,12 @@
!!! tip
Поиск в глубину можно реализовать и итеративно; заинтересованные читатели могут изучить это самостоятельно.
Поиск в глубину можно реализовать и итеративно. Заинтересованные читатели могут изучить это самостоятельно.
На рисунках ниже показан рекурсивный процесс прямого обхода двоичного дерева. Его можно разделить на две противоположные части: "вход в рекурсию" и "возврат".
На рисунке ниже показан рекурсивный процесс прямого обхода двоичного дерева. Его можно разделить на две противоположные части: «вход в рекурсию» и «возврат».
1. "Вход в рекурсию" означает запуск нового вызова функции; в этом процессе программа переходит к следующему узлу.
2. "Возврат" означает завершение вызова функции и возврат назад, то есть текущий узел уже полностью обработан.
1. «Вход в рекурсию» означает запуск нового вызова функции. В этом процессе программа переходит к следующему узлу.
2. «Возврат» означает завершение вызова функции и возврат назад, то есть текущий узел уже полностью обработан.
=== "<1>"
![Рекурсивный процесс прямого обхода](binary_tree_traversal.assets/preorder_step1.png)
+2 -2
View File
@@ -5,5 +5,5 @@
!!! abstract
Высокое дерево полно жизни: мощные корни, густая листва и раскидистые ветви.
Оно наглядно показывает нам живую форму данных, построенную на принципе "разделяй и властвуй".
Оно наглядно показывает нам живую форму данных, построенную на принципе «разделяй и властвуй».
+7 -7
View File
@@ -2,15 +2,15 @@
### Основные моменты
- Двоичное дерево - это нелинейная структура данных, отражающая логику "разделяй и властвуй". Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам.
- Двоичное дерево - это нелинейная структура данных, отражающая логику «разделяй и властвуй». Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам.
- Для любого узла двоичного дерева дерево, образованное его левым (правым) дочерним узлом и всеми нижележащими узлами, называется левым (правым) поддеревом этого узла.
- К связанным с двоичным деревом терминам относятся корневой узел, листовой узел, уровень, степень, ребро, высота, глубина и так далее.
- Инициализация двоичного дерева, вставка узлов и удаление узлов аналогичны операциям со связным списком.
- К распространенным видам двоичного дерева относятся идеальное двоичное дерево, полное двоичное дерево, строгое двоичное дерево и сбалансированное двоичное дерево. Идеальное двоичное дерево - наиболее желательное состояние, а связный список - худший случай после вырождения.
- Двоичное дерево можно представить массивом: значения узлов и пустые позиции располагаются в порядке обхода по уровням, а связи между родителем и детьми реализуются через индексацию.
- Обход двоичного дерева по уровням является методом поиска в ширину; он отражает идею "расширяться от центра к периферии слой за слоем" и обычно реализуется через очередь.
- Прямой, симметричный и обратный обходы относятся к поиску в глубину; они отражают идею "сначала дойти до конца, затем вернуться и продолжить" и обычно реализуются рекурсивно.
- Двоичное дерево поиска - это эффективная структура данных для поиска элементов; его поиск, вставка и удаление имеют временную сложность $O(\log n)$ . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до $O(n)$ .
- Обход двоичного дерева по уровням является методом поиска в ширину. Он отражает идею «расширяться от центра к периферии слой за слоем» и обычно реализуется через очередь.
- Прямой, симметричный и обратный обходы относятся к поиску в глубину. Они отражают идею «сначала дойти до конца, затем вернуться и продолжить» и обычно реализуются рекурсивно.
- Двоичное дерево поиска - это эффективная структура данных для поиска элементов. Его поиск, вставка и удаление имеют временную сложность $O(\log n)$ . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до $O(n)$ .
- AVL-дерево, также называемое сбалансированным двоичным деревом поиска, с помощью вращений гарантирует, что после постоянных вставок и удалений узлов дерево остается сбалансированным.
- Вращения AVL-дерева включают правое вращение, левое вращение, сначала правое затем левое и сначала левое затем правое. После вставки или удаления узла AVL-дерево выполняет вращения снизу вверх, чтобы снова восстановить баланс.
@@ -18,15 +18,15 @@
**Q**: Для двоичного дерева, состоящего из одного узла, высота дерева и глубина корня обе равны $0$ ?
Да, потому что высота и глубина обычно определяются как "число пройденных ребер".
Да, потому что высота и глубина обычно определяются как «число пройденных ребер».
**Q**: Вставка и удаление в двоичном дереве обычно выполняются в составе набора операций. Что именно означает этот "набор операций"? Можно ли понимать это как освобождение ресурсов у дочерних узлов ресурса?
**Q**: Вставка и удаление в двоичном дереве обычно выполняются в составе набора операций. Что именно означает этот «набор операций»? Можно ли понимать это как освобождение ресурсов у дочерних узлов ресурса?
Возьмем в качестве примера двоичное дерево поиска: операция удаления узла делится на три случая, и каждый из этих случаев требует нескольких последовательных шагов работы с узлами.
**Q**: Почему у DFS для двоичного дерева есть три порядка: прямой, симметричный и обратный? Для чего они нужны?
Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы - это три способа обхода двоичного дерева, с помощью которых можно получить результаты в определенном порядке. Например, в двоичном дереве поиска, где соблюдается отношение `значение левого дочернего узла < значение корня < значение правого дочернего узла` , если обходить дерево с приоритетом "лево $\rightarrow$ корень $\rightarrow$ право", то получится упорядоченная последовательность узлов.
Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы - это три способа обхода двоичного дерева, с помощью которых можно получить результаты в определенном порядке. Например, в двоичном дереве поиска, где соблюдается отношение `значение левого дочернего узла < значение корня < значение правого дочернего узла` , если обходить дерево с приоритетом «лево $\rightarrow$ корень $\rightarrow$ право», то получится упорядоченная последовательность узлов.
**Q**: Правое вращение работает с отношениями между `node` , `child` и `grand_child` . А связь между `node` и его исходным родителем разве не нужно поддерживать? После правого вращения она ведь не оборвется?