mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-16 11:02:09 +08:00
build
This commit is contained in:
@@ -12,13 +12,13 @@ comments: true
|
||||
|
||||
Сначала разберем простой случай. Если дана идеальная двоичная структура и все ее узлы хранятся в массиве в порядке обхода по уровням, то каждому узлу будет соответствовать единственный индекс массива.
|
||||
|
||||
Из свойств обхода по уровням можно вывести "формулу соответствия" между индексом родителя и индексами дочерних узлов: **если индекс некоторого узла равен $i$ , то индекс его левого дочернего узла равен $2i + 1$ , а правого - $2i + 2$** . На рисунке 7-12 показано соответствие между индексами разных узлов.
|
||||
Из свойств обхода по уровням можно вывести формулу соответствия между индексом родителя и индексами дочерних узлов: **если индекс некоторого узла равен $i$ , то индекс его левого дочернего узла равен $2i + 1$ , а правого - $2i + 2$** . На рисунке 7-12 показано соответствие между индексами разных узлов.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> Рисунок 7-12 Представление идеального двоичного дерева массивом </p>
|
||||
|
||||
**Эта формула соответствия играет ту же роль, что и ссылки на узлы в связной структуре** . Имея любой узел в массиве, мы можем по формуле получить доступ к его левому и правому дочерним узлам.
|
||||
**Эта формула соответствия играет ту же роль, что и ссылки на узлы в связной структуре** . Имея любой узел в массиве, мы можем с ее помощью получить доступ к его левому и правому дочерним узлам.
|
||||
|
||||
## 7.3.2 Представление произвольного двоичного дерева
|
||||
|
||||
|
||||
@@ -2018,7 +2018,12 @@ AVL-дерево одновременно является и двоичным
|
||||
@root = insert_helper(@root, val)
|
||||
end
|
||||
|
||||
### Рекурсивная вставка узла (вспомогательный метод) ###
|
||||
### Вставка узла ###
|
||||
def insert(val)
|
||||
@root = insert_helper(@root, val)
|
||||
end
|
||||
|
||||
# ## Рекурсивная вставка узла (вспомогательный метод) ###
|
||||
def insert_helper(node, val)
|
||||
return TreeNode.new(val) if node.nil?
|
||||
# 1. Найти позицию вставки и вставить узел
|
||||
@@ -2606,7 +2611,12 @@ AVL-дерево одновременно является и двоичным
|
||||
@root = remove_helper(@root, val)
|
||||
end
|
||||
|
||||
### Рекурсивное удаление узла (вспомогательный метод) ###
|
||||
### Удаление узла ###
|
||||
def remove(val)
|
||||
@root = remove_helper(@root, val)
|
||||
end
|
||||
|
||||
# ## Рекурсивное удаление узла (вспомогательный метод) ###
|
||||
def remove_helper(node, val)
|
||||
return if node.nil?
|
||||
# 1. Найти узел и удалить его
|
||||
|
||||
@@ -39,7 +39,7 @@ comments: true
|
||||
|
||||
<p align="center"> Рисунок 7-17 Пример поиска узла в двоичном дереве поиска </p>
|
||||
|
||||
Операция поиска в двоичном дереве поиска работает по тому же принципу, что и бинарный поиск: на каждом шаге она отбрасывает половину вариантов. Число итераций не превосходит высоты двоичного дерева, а когда дерево сбалансировано, требуется $O(\log n)$ времени. Пример кода приведен ниже:
|
||||
Операция поиска в двоичном дереве поиска работает по тому же принципу, что и двоичный поиск: на каждом шаге она отбрасывает половину вариантов. Число итераций не превосходит высоты двоичного дерева, а когда дерево сбалансировано, требуется $O(\log n)$ времени. Пример кода приведен ниже:
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -1607,7 +1607,7 @@ comments: true
|
||||
|
||||
## 7.4.2 Эффективность двоичного дерева поиска
|
||||
|
||||
Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок, поэтому его производительность стабильна и высока. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.
|
||||
Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок и обеспечивает стабильную высокую производительность. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.
|
||||
|
||||
<p align="center"> Таблица 7-2 Сравнение эффективности массива и дерева поиска </p>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ comments: true
|
||||
|
||||
# 7.1 Двоичное дерево
|
||||
|
||||
<u>Двоичное дерево (binary tree)</u> - это нелинейная структура данных, представляющая отношения порождения между "предками" и "потомками" и отражающая логику "разделения надвое". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.
|
||||
<u>Двоичное дерево (binary tree)</u> - это нелинейная структура данных, представляющая отношения между "предками" и "потомками" и отражающая логику "разделяй и властвуй". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -207,7 +207,7 @@ comments: true
|
||||
|
||||
Каждый узел имеет две ссылки (указателя), которые соответственно указывают на <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 и дерево ниже него".
|
||||
**Узлы, не имеющие дочерних узлов, называют листьями, а все остальные узлы содержат дочерние узлы и непустые поддеревья**. Как показано на рисунке 7-1, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него".
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -232,7 +232,7 @@ comments: true
|
||||
|
||||
!!! tip
|
||||
|
||||
Обрати внимание: обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 .
|
||||
Обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 .
|
||||
|
||||
## 7.1.2 Базовые операции двоичного дерева
|
||||
|
||||
@@ -631,7 +631,7 @@ comments: true
|
||||
|
||||
!!! tip
|
||||
|
||||
Обрати внимание: вставка узла может изменить исходную логическую структуру двоичного дерева, а удаление узла обычно означает удаление этого узла вместе со всеми его поддеревьями. Поэтому в двоичном дереве операции вставки и удаления обычно являются частью более крупного набора операций, который и реализует осмысленное действие.
|
||||
Стоит помнить, что вставка узла может изменить исходную логическую структуру двоичного дерева, а удаление узла обычно означает удаление этого узла вместе со всеми его поддеревьями. Поэтому в двоичном дереве операции вставки и удаления обычно являются частью более крупного набора операций, который и реализует осмысленное действие.
|
||||
|
||||
## 7.1.3 Распространенные типы двоичных деревьев
|
||||
|
||||
@@ -641,7 +641,7 @@ comments: true
|
||||
|
||||
!!! tip
|
||||
|
||||
Обрати внимание: в китайскоязычном сообществе идеальное двоичное дерево часто называют <u>полностью заполненным двоичным деревом</u>.
|
||||
В китайскоязычном сообществе идеальное двоичное дерево часто называют <u>полностью заполненным двоичным деревом</u>.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -649,7 +649,7 @@ comments: true
|
||||
|
||||
### 2. Полное двоичное дерево
|
||||
|
||||
Как показано на рисунке 7-5, <u>полное двоичное дерево (complete binary tree)</u> допускает неполное заполнение только на самом нижнем уровне, причем узлы этого уровня должны непрерывно заполняться слева направо. Обрати внимание: идеальное двоичное дерево тоже является полным двоичным деревом.
|
||||
Как показано на рисунке 7-5, <u>полное двоичное дерево (complete binary tree)</u> допускает неполное заполнение только на самом нижнем уровне, причем узлы этого уровня должны непрерывно заполняться слева направо. Стоит отметить, что идеальное двоичное дерево тоже является полным двоичным деревом.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -675,7 +675,7 @@ comments: true
|
||||
|
||||
На рисунке 7-8 показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем "идеальное двоичное дерево"; когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в "связный список".
|
||||
|
||||
- Идеальное двоичное дерево соответствует лучшему случаю и позволяет полностью раскрыть преимущества двоичного дерева с точки зрения "разделяй и властвуй".
|
||||
- Идеальное двоичное дерево соответствует лучшему случаю и позволяет в полной мере раскрыть преимущества подхода "разделяй и властвуй".
|
||||
- Связный список представляет противоположную крайность: все операции становятся линейными, а временная сложность деградирует до $O(n)$ .
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -12,7 +12,7 @@ comments: true
|
||||
|
||||
Как показано на рисунке 7-9, <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>; он отражает идею "расширяться от центра к периферии слой за слоем".
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -346,9 +346,9 @@ comments: true
|
||||
|
||||
## 7.2.2 Прямой, симметричный и обратный обходы
|
||||
|
||||
Соответственно, прямой, симметричный и обратный обходы относятся к <u>обходу в глубину (depth-first traversal)</u>, также называемому <u>поиском в глубину (depth-first search, DFS)</u>; он отражает идею "сначала идти до конца, затем откатываться и продолжать".
|
||||
Соответственно, прямой, симметричный и обратный обходы относятся к <u>обходу в глубину (depth-first traversal)</u>, также называемому <u>поиском в глубину (depth-first search, DFS)</u>; он отражает идею "сначала идти до конца, затем возвращаться и продолжать".
|
||||
|
||||
На рисунке 7-10 показан принцип работы обхода двоичного дерева в глубину. **Обход в глубину похож на то, как будто мы обходим всю двоичную структуру по внешнему контуру** , и у каждого узла встречаем три позиции, соответствующие прямому, симметричному и обратному обходам.
|
||||
На рисунке 7-10 показан принцип работы обхода двоичного дерева в глубину. **Обход в глубину можно представить как обход всей двоичной структуры по внешнему контуру** , и у каждого узла встречаются три позиции, соответствующие прямому, симметричному и обратному обходам.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ icon: material/graph-outline
|
||||
|
||||
!!! abstract
|
||||
|
||||
Высокое дерево полно жизни: мощные корни, густая крона и раскидистые ветви.
|
||||
Высокое дерево полно жизни: мощные корни, густая листва и раскидистые ветви.
|
||||
|
||||
Оно наглядно показывает нам форму данных, построенную на принципе "разделяй и властвуй".
|
||||
Оно наглядно показывает нам живую форму данных, построенную на принципе "разделяй и властвуй".
|
||||
|
||||
## Содержание главы
|
||||
|
||||
- [7.1 Двоичное дерево](binary_tree.md)
|
||||
- [7.2 Обход двоичного дерева](binary_tree_traversal.md)
|
||||
- [7.3 Представление дерева массивом](array_representation_of_tree.md)
|
||||
- [7.3 Представление двоичного дерева массивом](array_representation_of_tree.md)
|
||||
- [7.4 Двоичное дерево поиска](binary_search_tree.md)
|
||||
- [7.5 AVL-дерево *](avl_tree.md)
|
||||
- [7.6 Резюме](summary.md)
|
||||
- [7.6 Краткие итоги](summary.md)
|
||||
|
||||
@@ -6,14 +6,14 @@ comments: true
|
||||
|
||||
### 1. Основные моменты
|
||||
|
||||
- Двоичное дерево - это нелинейная структура данных, отражающая логику "разделения надвое". Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам.
|
||||
- Двоичное дерево - это нелинейная структура данных, отражающая логику "разделяй и властвуй". Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам.
|
||||
- Для любого узла двоичного дерева дерево, образованное его левым (правым) дочерним узлом и всеми нижележащими узлами, называется левым (правым) поддеревом этого узла.
|
||||
- К связанным с двоичным деревом терминам относятся корневой узел, листовой узел, уровень, степень, ребро, высота, глубина и так далее.
|
||||
- Инициализация двоичного дерева, вставка узлов и удаление узлов похожи по способу реализации на операции со связным списком.
|
||||
- Инициализация двоичного дерева, вставка узлов и удаление узлов аналогичны операциям со связным списком.
|
||||
- К распространенным видам двоичного дерева относятся идеальное двоичное дерево, полное двоичное дерево, строгое двоичное дерево и сбалансированное двоичное дерево. Идеальное двоичное дерево - наиболее желательное состояние, а связный список - худший случай после вырождения.
|
||||
- Двоичное дерево можно представить массивом: значения узлов и пустые позиции располагаются в порядке обхода по уровням, а связи между родителем и детьми реализуются через отображение индексов.
|
||||
- Обход двоичного дерева по уровням является методом поиска в ширину; он отражает идею "расширяться слой за слоем наружу" и обычно реализуется через очередь.
|
||||
- Прямой, симметричный и обратный обходы относятся к поиску в глубину; они отражают идею "сначала дойти до конца, затем откатиться и продолжить" и обычно реализуются рекурсивно.
|
||||
- Двоичное дерево можно представить массивом: значения узлов и пустые позиции располагаются в порядке обхода по уровням, а связи между родителем и детьми реализуются через индексацию.
|
||||
- Обход двоичного дерева по уровням является методом поиска в ширину; он отражает идею "расширяться от центра к периферии слой за слоем" и обычно реализуется через очередь.
|
||||
- Прямой, симметричный и обратный обходы относятся к поиску в глубину; они отражают идею "сначала дойти до конца, затем вернуться и продолжить" и обычно реализуются рекурсивно.
|
||||
- Двоичное дерево поиска - это эффективная структура данных для поиска элементов; его поиск, вставка и удаление имеют временную сложность $O(\log n)$ . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до $O(n)$ .
|
||||
- AVL-дерево, также называемое сбалансированным двоичным деревом поиска, с помощью вращений гарантирует, что после постоянных вставок и удалений узлов дерево остается сбалансированным.
|
||||
- Вращения AVL-дерева включают правое вращение, левое вращение, сначала правое затем левое и сначала левое затем правое. После вставки или удаления узла AVL-дерево выполняет вращения снизу вверх, чтобы снова восстановить баланс.
|
||||
|
||||
Reference in New Issue
Block a user