mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-17 17:26:06 +00:00
deploy
This commit is contained in:
@@ -4389,7 +4389,7 @@
|
||||
|
||||
<p><strong>Эта формула соответствия играет ту же роль, что и ссылки на узлы в связной структуре</strong> . Имея любой узел в массиве, мы можем с ее помощью получить доступ к его левому и правому дочерним узлам.</p>
|
||||
<h2 id="732">7.3.2 Представление произвольного двоичного дерева<a class="headerlink" href="#732" title="Permanent link">¶</a></h2>
|
||||
<p>Идеальное двоичное дерево - лишь частный случай; в обычной двоичной структуре на промежуточных уровнях часто существует множество <code>None</code> . Поскольку последовательность обхода по уровням не содержит этих <code>None</code> , мы не можем по одной лишь этой последовательности определить их количество и расположение. <strong>Это означает, что одному и тому же обходу по уровням может соответствовать сразу несколько различных структур двоичного дерева</strong>.</p>
|
||||
<p>Идеальное двоичное дерево - лишь частный случай. В обычной двоичной структуре на промежуточных уровнях часто существует множество <code>None</code> . Поскольку последовательность обхода по уровням не содержит этих <code>None</code> , мы не можем по одной лишь этой последовательности определить их количество и расположение. <strong>Это означает, что одному и тому же обходу по уровням может соответствовать сразу несколько различных структур двоичного дерева</strong>.</p>
|
||||
<p>Как показано на рисунке 7-13, для неполной двоичной структуры описанный выше способ представления массивом уже перестает работать.</p>
|
||||
<p><img alt="Одной последовательности обхода по уровням соответствуют разные двоичные структуры" class="animation-figure" src="../array_representation_of_tree.assets/array_representation_without_empty.png" /></p>
|
||||
<p align="center"> Рисунок 7-13 Одной последовательности обхода по уровням соответствуют разные двоичные структуры </p>
|
||||
|
||||
@@ -4657,7 +4657,7 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="75-avl-">7.5 AVL-дерево *<a class="headerlink" href="#75-avl-" title="Permanent link">¶</a></h1>
|
||||
<p>В разделе "Двоичное дерево поиска" мы упоминали, что после многократных операций вставки и удаления узлов двоичное дерево поиска может выродиться в связный список. В таком случае временная сложность всех операций ухудшается с <span class="arithmatex">\(O(\log n)\)</span> до <span class="arithmatex">\(O(n)\)</span> .</p>
|
||||
<p>В разделе «Двоичное дерево поиска» мы упоминали, что после многократных операций вставки и удаления узлов двоичное дерево поиска может выродиться в связный список. В таком случае временная сложность всех операций ухудшается с <span class="arithmatex">\(O(\log n)\)</span> до <span class="arithmatex">\(O(n)\)</span> .</p>
|
||||
<p>Как показано на рисунке 7-24, после двух операций удаления узлов это двоичное дерево поиска вырождается в связный список.</p>
|
||||
<p><img alt="Деградация AVL-дерева после удаления узлов" class="animation-figure" src="../avl_tree.assets/avltree_degradation_from_removing_node.png" /></p>
|
||||
<p align="center"> Рисунок 7-24 Деградация AVL-дерева после удаления узлов </p>
|
||||
@@ -4666,7 +4666,7 @@
|
||||
<p><img alt="Деградация AVL-дерева после вставки узлов" class="animation-figure" src="../avl_tree.assets/avltree_degradation_from_inserting_node.png" /></p>
|
||||
<p align="center"> Рисунок 7-25 Деградация AVL-дерева после вставки узлов </p>
|
||||
|
||||
<p>В 1962 году Г. М. Adelson-Velsky и Е. М. Landis в статье "An algorithm for the organization of information" предложили <u>AVL-дерево</u>. В статье подробно описан набор операций, гарантирующий, что при непрерывном добавлении и удалении узлов AVL-дерево не вырождается, благодаря чему временная сложность различных операций сохраняется на уровне <span class="arithmatex">\(O(\log n)\)</span> . Иначе говоря, в сценариях, где часто выполняются вставка, удаление, поиск и изменение, AVL-дерево всегда поддерживает эффективную работу с данными и потому имеет высокую практическую ценность.</p>
|
||||
<p>В 1962 году Г. М. Adelson-Velsky и Е. М. Landis в статье «An algorithm for the organization of information» предложили <u>AVL-дерево</u>. В статье подробно описан набор операций, гарантирующий, что при непрерывном добавлении и удалении узлов AVL-дерево не вырождается, благодаря чему временная сложность различных операций сохраняется на уровне <span class="arithmatex">\(O(\log n)\)</span> . Иначе говоря, в сценариях, где часто выполняются вставка, удаление, поиск и изменение, AVL-дерево всегда поддерживает эффективную работу с данными и потому имеет высокую практическую ценность.</p>
|
||||
<h2 id="751-avl-">7.5.1 Распространенные термины AVL-дерева<a class="headerlink" href="#751-avl-" title="Permanent link">¶</a></h2>
|
||||
<p>AVL-дерево одновременно является и двоичным деревом поиска, и сбалансированным двоичным деревом, то есть одновременно удовлетворяет всем свойствам обеих этих структур. Поэтому AVL-дерево является разновидностью <u>сбалансированного двоичного дерева поиска (balanced binary search tree)</u>.</p>
|
||||
<h3 id="1">1. Высота узла<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
@@ -4857,7 +4857,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>"Высота узла" означает расстояние от этого узла до самого удаленного листового узла, то есть число пройденных "ребер". Особенно важно помнить, что высота листового узла равна <span class="arithmatex">\(0\)</span> , а высота пустого узла равна <span class="arithmatex">\(-1\)</span> . Мы создадим две вспомогательные функции: одну для получения высоты узла, другую для ее обновления:</p>
|
||||
<p>«Высота узла» означает расстояние от этого узла до самого удаленного листового узла, то есть число пройденных «ребер». Особенно важно помнить, что высота листового узла равна <span class="arithmatex">\(0\)</span> , а высота пустого узла равна <span class="arithmatex">\(-1\)</span> . Мы создадим две вспомогательные функции: одну для получения высоты узла, другую для ее обновления:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="2:13"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Kotlin</label><label for="__tabbed_2_13">Ruby</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5074,7 +5074,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="2-">2. Баланс-фактор узла<a class="headerlink" href="#2-" title="Permanent link">¶</a></h3>
|
||||
<p><u>Баланс-фактор (balance factor)</u> узла определяется как высота левого поддерева минус высота правого поддерева; при этом баланс-фактор пустого узла считается равным <span class="arithmatex">\(0\)</span> . Мы также инкапсулируем получение баланс-фактора в отдельную функцию, чтобы потом было удобнее ее использовать:</p>
|
||||
<p><u>Баланс-фактор (balance factor)</u> узла определяется как высота левого поддерева минус высота правого поддерева. При этом баланс-фактор пустого узла считается равным <span class="arithmatex">\(0\)</span> . Мы также инкапсулируем получение баланс-фактора в отдельную функцию, чтобы потом было удобнее ее использовать:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="3:13"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><input id="__tabbed_3_11" name="__tabbed_3" type="radio" /><input id="__tabbed_3_12" name="__tabbed_3" type="radio" /><input id="__tabbed_3_13" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Python</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Java</label><label for="__tabbed_3_4">C#</label><label for="__tabbed_3_5">Go</label><label for="__tabbed_3_6">Swift</label><label for="__tabbed_3_7">JS</label><label for="__tabbed_3_8">TS</label><label for="__tabbed_3_9">Dart</label><label for="__tabbed_3_10">Rust</label><label for="__tabbed_3_11">C</label><label for="__tabbed_3_12">Kotlin</label><label for="__tabbed_3_13">Ruby</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5222,13 +5222,13 @@
|
||||
</div>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>Пусть баланс-фактор равен <span class="arithmatex">\(f\)</span> ; тогда для любого узла AVL-дерева выполняется <span class="arithmatex">\(-1 \le f \le 1\)</span> .</p>
|
||||
<p>Пусть баланс-фактор равен <span class="arithmatex">\(f\)</span>. Тогда для любого узла AVL-дерева выполняется <span class="arithmatex">\(-1 \le f \le 1\)</span> .</p>
|
||||
</div>
|
||||
<h2 id="752-avl-">7.5.2 Вращения AVL-дерева<a class="headerlink" href="#752-avl-" title="Permanent link">¶</a></h2>
|
||||
<p>Особенность AVL-дерева заключается в операции "вращения", которая позволяет заново сбалансировать разбалансированный узел, не нарушая последовательность симметричного обхода двоичного дерева. Иначе говоря, <strong>операция вращения одновременно сохраняет свойство "двоичного дерева поиска" и возвращает дерево в состояние "сбалансированного двоичного дерева"</strong>.</p>
|
||||
<p>Узлы, для которых абсолютное значение баланс-фактора больше <span class="arithmatex">\(1\)</span> , мы называем "разбалансированными узлами". В зависимости от вида разбаланса вращения делятся на четыре типа: правое вращение, левое вращение, сначала левое затем правое, и сначала правое затем левое. Ниже разберем их подробно.</p>
|
||||
<p>Особенность AVL-дерева заключается в операции «вращения», которая позволяет заново сбалансировать разбалансированный узел, не нарушая последовательность симметричного обхода двоичного дерева. Иначе говоря, <strong>операция вращения одновременно сохраняет свойство «двоичного дерева поиска» и возвращает дерево в состояние «сбалансированного двоичного дерева»</strong>.</p>
|
||||
<p>Узлы, для которых абсолютное значение баланс-фактора больше <span class="arithmatex">\(1\)</span> , мы называем «разбалансированными узлами». В зависимости от вида разбаланса вращения делятся на четыре типа: правое вращение, левое вращение, сначала левое затем правое, и сначала правое затем левое. Ниже разберем их подробно.</p>
|
||||
<h3 id="1_1">1. Правое вращение<a class="headerlink" href="#1_1" title="Permanent link">¶</a></h3>
|
||||
<p>Как показано на рисунках ниже, под узлом указан его баланс-фактор. Если двигаться снизу вверх, то первым разбалансированным узлом в двоичном дереве будет "узел 3". Рассмотрим поддерево с этим узлом в качестве корня, обозначим данный узел как <code>node</code> , его левого дочернего узла как <code>child</code> и выполним "правое вращение". После завершения правого вращения поддерево снова станет сбалансированным и при этом сохранит свойство двоичного дерева поиска.</p>
|
||||
<p>Как показано на рисунке 7-26, под узлом указан его баланс-фактор. Если двигаться снизу вверх, то первым разбалансированным узлом в двоичном дереве будет «узел 3». Рассмотрим поддерево с этим узлом в качестве корня, обозначим данный узел как <code>node</code> , его левого дочернего узла как <code>child</code> и выполним «правое вращение». После завершения правого вращения поддерево снова станет сбалансированным и при этом сохранит свойство двоичного дерева поиска.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="4:4"><input checked="checked" id="__tabbed_4_1" name="__tabbed_4" type="radio" /><input id="__tabbed_4_2" name="__tabbed_4" type="radio" /><input id="__tabbed_4_3" name="__tabbed_4" type="radio" /><input id="__tabbed_4_4" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1"><1></label><label for="__tabbed_4_2"><2></label><label for="__tabbed_4_3"><3></label><label for="__tabbed_4_4"><4></label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5251,7 +5251,7 @@
|
||||
<p><img alt="Правое вращение при наличии grand_child" class="animation-figure" src="../avl_tree.assets/avltree_right_rotate_with_grandchild.png" /></p>
|
||||
<p align="center"> Рисунок 7-27 Правое вращение при наличии grand_child </p>
|
||||
|
||||
<p>"Поворот вправо" - это лишь образное описание; в реальности он реализуется через изменение указателей узлов. Код приведен ниже:</p>
|
||||
<p>«Поворот вправо» - это лишь образное описание. В реальности он реализуется через изменение указателей узлов. Код приведен ниже:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="5:13"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><input id="__tabbed_5_7" name="__tabbed_5" type="radio" /><input id="__tabbed_5_8" name="__tabbed_5" type="radio" /><input id="__tabbed_5_9" name="__tabbed_5" type="radio" /><input id="__tabbed_5_10" name="__tabbed_5" type="radio" /><input id="__tabbed_5_11" name="__tabbed_5" type="radio" /><input id="__tabbed_5_12" name="__tabbed_5" type="radio" /><input id="__tabbed_5_13" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">Python</label><label for="__tabbed_5_2">C++</label><label for="__tabbed_5_3">Java</label><label for="__tabbed_5_4">C#</label><label for="__tabbed_5_5">Go</label><label for="__tabbed_5_6">Swift</label><label for="__tabbed_5_7">JS</label><label for="__tabbed_5_8">TS</label><label for="__tabbed_5_9">Dart</label><label for="__tabbed_5_10">Rust</label><label for="__tabbed_5_11">C</label><label for="__tabbed_5_12">Kotlin</label><label for="__tabbed_5_13">Ruby</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5470,11 +5470,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="2">2. Левое вращение<a class="headerlink" href="#2" title="Permanent link">¶</a></h3>
|
||||
<p>Соответственно, если рассмотреть "зеркальную" версию приведенного выше разбалансированного двоичного дерева, то понадобится выполнить "левое вращение", показанное на рисунке 7-28.</p>
|
||||
<p>Соответственно, если рассмотреть «зеркальную» версию приведенного выше разбалансированного двоичного дерева, то понадобится выполнить «левое вращение», показанное на рисунке 7-28.</p>
|
||||
<p><img alt="Левое вращение" class="animation-figure" src="../avl_tree.assets/avltree_left_rotate.png" /></p>
|
||||
<p align="center"> Рисунок 7-28 Левое вращение </p>
|
||||
|
||||
<p>По той же причине, когда у узла <code>child</code> есть левый дочерний узел, который обозначим как <code>grand_child</code> , в левое вращение также требуется добавить шаг: сделать <code>grand_child</code> правым дочерним узлом <code>node</code> .</p>
|
||||
<p>Аналогичная ситуация показана на рисунке 7-29. Если у узла <code>child</code> есть левый дочерний узел, который обозначим как <code>grand_child</code> , то в левое вращение также требуется добавить шаг: сделать <code>grand_child</code> правым дочерним узлом <code>node</code> .</p>
|
||||
<p><img alt="Левое вращение при наличии grand_child" class="animation-figure" src="../avl_tree.assets/avltree_left_rotate_with_grandchild.png" /></p>
|
||||
<p align="center"> Рисунок 7-29 Левое вращение при наличии grand_child </p>
|
||||
|
||||
@@ -5697,21 +5697,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="3">3. Сначала левое, затем правое вращение<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||
<p>Для разбалансированного узла 3 на рисунке 7-30 ни одно лишь левое вращение, ни одно лишь правое вращение не способны вернуть поддерево в баланс. В этом случае нужно сначала выполнить "левое вращение" для <code>child</code> , а затем выполнить "правое вращение" для <code>node</code> .</p>
|
||||
<p>Для разбалансированного узла 3 на рисунке 7-30 ни одно лишь левое вращение, ни одно лишь правое вращение не способны вернуть поддерево в баланс. В этом случае нужно сначала выполнить «левое вращение» для <code>child</code> , а затем выполнить «правое вращение» для <code>node</code> .</p>
|
||||
<p><img alt="Сначала левое, затем правое вращение" class="animation-figure" src="../avl_tree.assets/avltree_left_right_rotate.png" /></p>
|
||||
<p align="center"> Рисунок 7-30 Сначала левое, затем правое вращение </p>
|
||||
|
||||
<h3 id="4">4. Сначала правое, затем левое вращение<a class="headerlink" href="#4" title="Permanent link">¶</a></h3>
|
||||
<p>Как показано на рисунке 7-31, для зеркальной ситуации предыдущего разбалансированного двоичного дерева нужно сначала выполнить "правое вращение" для <code>child</code> , а затем "левое вращение" для <code>node</code> .</p>
|
||||
<p>Как показано на рисунке 7-31, для зеркальной ситуации предыдущего разбалансированного двоичного дерева нужно сначала выполнить «правое вращение» для <code>child</code> , а затем «левое вращение» для <code>node</code> .</p>
|
||||
<p><img alt="Сначала правое, затем левое вращение" class="animation-figure" src="../avl_tree.assets/avltree_right_left_rotate.png" /></p>
|
||||
<p align="center"> Рисунок 7-31 Сначала правое, затем левое вращение </p>
|
||||
|
||||
<h3 id="5">5. Выбор вращения<a class="headerlink" href="#5" title="Permanent link">¶</a></h3>
|
||||
<p>Четыре вида разбаланса, показанные на рисунке 7-32, по одному соответствуют рассмотренным выше случаям; для них соответственно требуются правое вращение, сначала левое затем правое, сначала правое затем левое и левое вращение.</p>
|
||||
<p>Четыре вида разбаланса, показанные на рисунке 7-32, по одному соответствуют рассмотренным выше случаям. Для них соответственно требуются правое вращение, сначала левое затем правое, сначала правое затем левое и левое вращение.</p>
|
||||
<p><img alt="Четыре случая вращений AVL-дерева" class="animation-figure" src="../avl_tree.assets/avltree_rotation_cases.png" /></p>
|
||||
<p align="center"> Рисунок 7-32 Четыре случая вращений AVL-дерева </p>
|
||||
|
||||
<p>Как показано в таблице 7-3, мы определяем, какому из этих четырех случаев соответствует разбалансированный узел, по знаку баланс-фактора самого разбалансированного узла и по знаку баланс-фактора дочернего узла на более высокой стороне.</p>
|
||||
<p>Как показано в таблице 7-3, мы определяем, какому из случаев на рисунке 7-32 соответствует разбалансированный узел, по знаку баланс-фактора самого разбалансированного узла и по знаку баланс-фактора дочернего узла на более высокой стороне.</p>
|
||||
<p align="center"> Таблица 7-3 Условия выбора для четырех случаев вращений </p>
|
||||
|
||||
<div class="center-table">
|
||||
|
||||
@@ -4490,7 +4490,7 @@
|
||||
<h2 id="741">7.4.1 Операции с двоичным деревом поиска<a class="headerlink" href="#741" title="Permanent link">¶</a></h2>
|
||||
<p>Мы инкапсулируем двоичное дерево поиска в класс <code>BinarySearchTree</code> и объявляем переменную-член <code>root</code> , которая указывает на корневой узел дерева.</p>
|
||||
<h3 id="1">1. Поиск узла<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>Для заданного целевого значения узла <code>num</code> можно выполнить поиск, опираясь на свойства двоичного дерева поиска. Как показано на рисунках ниже, мы объявляем узел <code>cur</code> , стартуем от корня дерева <code>root</code> и циклически сравниваем значения <code>cur.val</code> и <code>num</code> .</p>
|
||||
<p>Для заданного целевого значения узла <code>num</code> можно выполнить поиск, опираясь на свойства двоичного дерева поиска. Как показано на рисунке 7-17, мы объявляем узел <code>cur</code> , стартуем от корня дерева <code>root</code> и циклически сравниваем значения <code>cur.val</code> и <code>num</code> .</p>
|
||||
<ul>
|
||||
<li>Если <code>cur.val < num</code> , это означает, что целевой узел находится в правом поддереве <code>cur</code> , поэтому выполняем <code>cur = cur.right</code> .</li>
|
||||
<li>Если <code>cur.val > num</code> , это означает, что целевой узел находится в левом поддереве <code>cur</code> , поэтому выполняем <code>cur = cur.left</code> .</li>
|
||||
@@ -4796,7 +4796,7 @@
|
||||
<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%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self%2C%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%27%5Cn%D0%9D%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%20%D1%83%D0%B7%D0%BB%D0%B0%20%3D%20%7B%7D%2C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20%3D%20%7B%7D%27.format%28node%2C%20node.val%29%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=162&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div></p>
|
||||
</details>
|
||||
<h3 id="2">2. Вставка узла<a class="headerlink" href="#2" title="Permanent link">¶</a></h3>
|
||||
<p>Пусть дан элемент <code>num</code> , который нужно вставить. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево < корень < правое поддерево", процесс вставки выглядит следующим образом.</p>
|
||||
<p>Пусть дан элемент <code>num</code> , который нужно вставить. Чтобы сохранить свойство двоичного дерева поиска «левое поддерево < корень < правое поддерево», процесс вставки показан на рисунке 7-18.</p>
|
||||
<ol>
|
||||
<li><strong>Найти позицию для вставки</strong>: как и в операции поиска, начиная от корня, мы циклически спускаемся вниз в зависимости от соотношения между текущим значением узла и <code>num</code> , пока не выйдем за листовой узел (то есть не дойдем до <code>None</code> ).</li>
|
||||
<li><strong>Вставить узел в найденную позицию</strong>: инициализировать узел <code>num</code> и поставить его на место этого <code>None</code> .</li>
|
||||
@@ -5237,7 +5237,7 @@
|
||||
</details>
|
||||
<p>Как и поиск узла, вставка узла требует <span class="arithmatex">\(O(\log n)\)</span> времени.</p>
|
||||
<h3 id="3">3. Удаление узла<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||
<p>Сначала нужно найти в двоичном дереве целевой узел, а затем удалить его. Как и при вставке, после удаления необходимо сохранить свойство двоичного дерева поиска: "левое поддерево < корень < правое поддерево". Поэтому в зависимости от числа дочерних узлов у удаляемого узла, то есть для случаев со степенью 0, 1 и 2, выполняются разные операции удаления.</p>
|
||||
<p>Сначала нужно найти в двоичном дереве целевой узел, а затем удалить его. Как и при вставке, после удаления необходимо сохранить свойство двоичного дерева поиска: «левое поддерево < корень < правое поддерево». Поэтому в зависимости от числа дочерних узлов у удаляемого узла, то есть для случаев со степенью 0, 1 и 2, выполняются разные операции удаления.</p>
|
||||
<p>Как показано на рисунке 7-19, когда степень удаляемого узла равна <span class="arithmatex">\(0\)</span> , это значит, что узел является листом и может быть удален напрямую.</p>
|
||||
<p><img alt="Удаление узла в двоичном дереве поиска (степень 0)" class="animation-figure" src="../binary_search_tree.assets/bst_remove_case1.png" /></p>
|
||||
<p align="center"> Рисунок 7-19 Удаление узла в двоичном дереве поиска (степень 0) </p>
|
||||
@@ -5246,10 +5246,10 @@
|
||||
<p><img alt="Удаление узла в двоичном дереве поиска (степень 1)" class="animation-figure" src="../binary_search_tree.assets/bst_remove_case2.png" /></p>
|
||||
<p align="center"> Рисунок 7-20 Удаление узла в двоичном дереве поиска (степень 1) </p>
|
||||
|
||||
<p>Когда степень удаляемого узла равна <span class="arithmatex">\(2\)</span> , мы уже не можем удалить его напрямую и должны использовать для замены другой узел. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево <span class="arithmatex">\(<\)</span> корень <span class="arithmatex">\(<\)</span> правое поддерево", <strong>этим узлом может быть минимальный узел правого поддерева или максимальный узел левого поддерева</strong>.</p>
|
||||
<p>Предположим, мы выбираем минимальный узел правого поддерева, то есть следующий узел в симметричном обходе. Тогда процесс удаления выглядит так.</p>
|
||||
<p>Когда степень удаляемого узла равна <span class="arithmatex">\(2\)</span> , мы уже не можем удалить его напрямую и должны использовать для замены другой узел. Чтобы сохранить свойство двоичного дерева поиска «левое поддерево <span class="arithmatex">\(<\)</span> корень <span class="arithmatex">\(<\)</span> правое поддерево», <strong>этим узлом может быть минимальный узел правого поддерева или максимальный узел левого поддерева</strong>.</p>
|
||||
<p>Предположим, мы выбираем минимальный узел правого поддерева, то есть следующий узел в симметричном обходе. Тогда процесс удаления показан на рисунке 7-21.</p>
|
||||
<ol>
|
||||
<li>Найти следующий узел в "последовательности симметричного обхода" для удаляемого узла и обозначить его как <code>tmp</code> .</li>
|
||||
<li>Найти следующий узел в «последовательности симметричного обхода» для удаляемого узла и обозначить его как <code>tmp</code> .</li>
|
||||
<li>Значением <code>tmp</code> перезаписать значение удаляемого узла, а затем рекурсивно удалить узел <code>tmp</code> из дерева.</li>
|
||||
</ol>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="4:4"><input checked="checked" id="__tabbed_4_1" name="__tabbed_4" type="radio" /><input id="__tabbed_4_2" name="__tabbed_4" type="radio" /><input id="__tabbed_4_3" name="__tabbed_4" type="radio" /><input id="__tabbed_4_4" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1"><1></label><label for="__tabbed_4_2"><2></label><label for="__tabbed_4_3"><3></label><label for="__tabbed_4_4"><4></label></div>
|
||||
@@ -5995,14 +5995,14 @@
|
||||
<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%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20%21%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%20%20%20%20bst.remove%281%29%0A%20%20%20%20bst.remove%282%29%0A%20%20%20%20bst.remove%284%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=162&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div></p>
|
||||
</details>
|
||||
<h3 id="4">4. Упорядоченность симметричного обхода<a class="headerlink" href="#4" title="Permanent link">¶</a></h3>
|
||||
<p>Как показано на рисунке 7-22, симметричный обход двоичного дерева следует порядку "лево <span class="arithmatex">\(\rightarrow\)</span> корень <span class="arithmatex">\(\rightarrow\)</span> право", а двоичное дерево поиска удовлетворяет соотношению "левый дочерний узел <span class="arithmatex">\(<\)</span> корень <span class="arithmatex">\(<\)</span> правый дочерний узел".</p>
|
||||
<p>Как показано на рисунке 7-22, симметричный обход двоичного дерева следует порядку «лево <span class="arithmatex">\(\rightarrow\)</span> корень <span class="arithmatex">\(\rightarrow\)</span> право», а двоичное дерево поиска удовлетворяет соотношению «левый дочерний узел <span class="arithmatex">\(<\)</span> корень <span class="arithmatex">\(<\)</span> правый дочерний узел».</p>
|
||||
<p>Это означает, что при симметричном обходе двоичного дерева поиска мы всегда сначала будем посещать следующий минимальный узел, и отсюда получается важное свойство: <strong>последовательность симметричного обхода двоичного дерева поиска является возрастающей</strong>.</p>
|
||||
<p>Используя это свойство возрастающей последовательности симметричного обхода, мы можем получить отсортированные данные из двоичного дерева поиска всего за <span class="arithmatex">\(O(n)\)</span> времени, без дополнительной сортировки, что очень эффективно.</p>
|
||||
<p><img alt="Последовательность симметричного обхода двоичного дерева поиска" class="animation-figure" src="../binary_search_tree.assets/bst_inorder_traversal.png" /></p>
|
||||
<p align="center"> Рисунок 7-22 Последовательность симметричного обхода двоичного дерева поиска </p>
|
||||
|
||||
<h2 id="742">7.4.2 Эффективность двоичного дерева поиска<a class="headerlink" href="#742" title="Permanent link">¶</a></h2>
|
||||
<p>Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок и обеспечивает стабильную высокую производительность. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.</p>
|
||||
<p>Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Как видно по данным в таблице 7-2, временная сложность операций двоичного дерева поиска имеет логарифмический порядок и обеспечивает стабильную высокую производительность. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.</p>
|
||||
<p align="center"> Таблица 7-2 Сравнение эффективности массива и дерева поиска </p>
|
||||
|
||||
<div class="center-table">
|
||||
@@ -6033,7 +6033,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p>В идеальном случае двоичное дерево поиска является "сбалансированным", и тогда любой узел можно найти за <span class="arithmatex">\(\log n\)</span> итераций.</p>
|
||||
<p>В идеальном случае двоичное дерево поиска является «сбалансированным», и тогда любой узел можно найти за <span class="arithmatex">\(\log n\)</span> итераций.</p>
|
||||
<p>Однако если в двоичное дерево поиска непрерывно вставлять и удалять узлы, оно может выродиться в связный список, как показано на рисунке 7-23. Тогда временная сложность различных операций тоже вырождается до <span class="arithmatex">\(O(n)\)</span> .</p>
|
||||
<p><img alt="Деградация двоичного дерева поиска" class="animation-figure" src="../binary_search_tree.assets/bst_degradation.png" /></p>
|
||||
<p align="center"> Рисунок 7-23 Деградация двоичного дерева поиска </p>
|
||||
|
||||
@@ -4557,7 +4557,7 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="71">7.1 Двоичное дерево<a class="headerlink" href="#71" title="Permanent link">¶</a></h1>
|
||||
<p><u>Двоичное дерево (binary tree)</u> - это нелинейная структура данных, представляющая отношения между "предками" и "потомками" и отражающая логику "разделяй и властвуй". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.</p>
|
||||
<p><u>Двоичное дерево (binary tree)</u> - это нелинейная структура данных, представляющая отношения между «предками» и «потомками» и отражающая логику «разделяй и властвуй». Подобно связному списку, базовой единицей двоичного дерева является узел. Каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:13"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" name="__tabbed_1" type="radio" /><input id="__tabbed_1_13" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Kotlin</label><label for="__tabbed_1_13">Ruby</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -4735,8 +4735,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Каждый узел имеет две ссылки (указателя), которые соответственно указывают на <u>левого дочернего узла (left-child node)</u> и <u>правого дочернего узла (right-child node)</u>; данный узел называется <u>родительским узлом (parent node)</u> для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется <u>левым поддеревом (left subtree)</u> этого узла; аналогично определяется <u>правое поддерево (right subtree)</u>.</p>
|
||||
<p><strong>Узлы, не имеющие дочерних узлов, называют листьями, а все остальные узлы содержат дочерние узлы и непустые поддеревья</strong>. Как показано на рисунке 7-1, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него".</p>
|
||||
<p>Каждый узел имеет две ссылки (указателя), которые соответственно указывают на <u>левого дочернего узла (left-child node)</u> и <u>правого дочернего узла (right-child node)</u>. Данный узел называется <u>родительским узлом (parent node)</u> для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется <u>левым поддеревом (left subtree)</u> этого узла. Аналогично определяется <u>правое поддерево (right subtree)</u>.</p>
|
||||
<p><strong>Узлы, не имеющие дочерних узлов, называют листьями, а все остальные узлы содержат дочерние узлы и непустые поддеревья</strong>. Как показано на рисунке 7-1, если рассматривать «узел 2» как родительский, то его левым и правым дочерними узлами будут «узел 4» и «узел 5». Левое поддерево - это «узел 4 и дерево ниже него», а правое поддерево - это «узел 5 и дерево ниже него».</p>
|
||||
<p><img alt="Родительский узел, дочерние узлы и поддеревья" class="animation-figure" src="../binary_tree.assets/binary_tree_definition.png" /></p>
|
||||
<p align="center"> Рисунок 7-1 Родительский узел, дочерние узлы и поддеревья </p>
|
||||
|
||||
@@ -4744,9 +4744,9 @@
|
||||
<p>Распространенные термины двоичного дерева показаны на рисунке 7-2.</p>
|
||||
<ul>
|
||||
<li><u>Корневой узел (root node)</u>: узел, расположенный на верхнем уровне двоичного дерева и не имеющий родительского узла.</li>
|
||||
<li><u>Листовой узел (leaf node)</u>: узел без дочерних узлов; оба его указателя направлены на <code>None</code> .</li>
|
||||
<li><u>Листовой узел (leaf node)</u>: узел без дочерних узлов. Оба его указателя направлены на <code>None</code> .</li>
|
||||
<li><u>Ребро (edge)</u>: отрезок, соединяющий два узла, то есть ссылка (указатель) между узлами.</li>
|
||||
<li><u>Уровень (level)</u> узла: увеличивается сверху вниз; уровень корневого узла равен 1 .</li>
|
||||
<li><u>Уровень (level)</u> узла: увеличивается сверху вниз. Уровень корневого узла равен 1 .</li>
|
||||
<li><u>Степень (degree)</u> узла: число дочерних узлов данного узла. В двоичном дереве возможны степени 0, 1, 2 .</li>
|
||||
<li><u>Высота (height)</u> двоичного дерева: число ребер от корневого узла до самого удаленного листового узла.</li>
|
||||
<li><u>Глубина (depth)</u> узла: число ребер от корневого узла до данного узла.</li>
|
||||
@@ -4757,7 +4757,7 @@
|
||||
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>Обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 .</p>
|
||||
<p>Обычно под «высотой» и «глубиной» понимают «число пройденных ребер», но в некоторых задачах или учебниках их могут определять как «число пройденных узлов». В таком случае и высоту, и глубину нужно увеличить на 1 .</p>
|
||||
</div>
|
||||
<h2 id="712">7.1.2 Базовые операции двоичного дерева<a class="headerlink" href="#712" title="Permanent link">¶</a></h2>
|
||||
<h3 id="1">1. Инициализация двоичного дерева<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
@@ -5110,7 +5110,7 @@
|
||||
</div>
|
||||
<h2 id="713">7.1.3 Распространенные типы двоичных деревьев<a class="headerlink" href="#713" title="Permanent link">¶</a></h2>
|
||||
<h3 id="1_1">1. Идеальное двоичное дерево<a class="headerlink" href="#1_1" title="Permanent link">¶</a></h3>
|
||||
<p>Как показано на рисунке 7-4, <u>идеальное двоичное дерево (perfect binary tree)</u> полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна <span class="arithmatex">\(0\)</span> , а у всех остальных узлов степень равна <span class="arithmatex">\(2\)</span> ; если высота дерева равна <span class="arithmatex">\(h\)</span> , то общее число узлов равно <span class="arithmatex">\(2^{h+1} - 1\)</span> , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления.</p>
|
||||
<p>Как показано на рисунке 7-4, <u>идеальное двоичное дерево (perfect binary tree)</u> полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна <span class="arithmatex">\(0\)</span> , а у всех остальных узлов степень равна <span class="arithmatex">\(2\)</span>. Если высота дерева равна <span class="arithmatex">\(h\)</span> , то общее число узлов равно <span class="arithmatex">\(2^{h+1} - 1\)</span> , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления.</p>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>В китайскоязычном сообществе идеальное двоичное дерево часто называют <u>полностью заполненным двоичным деревом</u>.</p>
|
||||
@@ -5134,9 +5134,9 @@
|
||||
<p align="center"> Рисунок 7-7 Сбалансированное двоичное дерево </p>
|
||||
|
||||
<h2 id="714">7.1.4 Вырождение двоичного дерева<a class="headerlink" href="#714" title="Permanent link">¶</a></h2>
|
||||
<p>На рисунке 7-8 показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем "идеальное двоичное дерево"; когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в "связный список".</p>
|
||||
<p>На рисунке 7-8 показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем «идеальное двоичное дерево». Когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в «связный список».</p>
|
||||
<ul>
|
||||
<li>Идеальное двоичное дерево соответствует лучшему случаю и позволяет в полной мере раскрыть преимущества подхода "разделяй и властвуй".</li>
|
||||
<li>Идеальное двоичное дерево соответствует лучшему случаю и позволяет в полной мере раскрыть преимущества подхода «разделяй и властвуй».</li>
|
||||
<li>Связный список представляет противоположную крайность: все операции становятся линейными, а временная сложность деградирует до <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
</ul>
|
||||
<p><img alt="Лучший и худший случаи структуры двоичного дерева" class="animation-figure" src="../binary_tree.assets/binary_tree_best_worst_cases.png" /></p>
|
||||
|
||||
@@ -4473,12 +4473,12 @@
|
||||
<p>К распространенным способам обхода двоичного дерева относятся обход по уровням, прямой обход, симметричный обход и обратный обход.</p>
|
||||
<h2 id="721">7.2.1 Обход по уровням<a class="headerlink" href="#721" title="Permanent link">¶</a></h2>
|
||||
<p>Как показано на рисунке 7-9, <u>обход по уровням (level-order traversal)</u> проходит двоичное дерево сверху вниз по уровням и на каждом уровне посещает узлы слева направо.</p>
|
||||
<p>По своей сути обход по уровням относится к <u>обходу в ширину (breadth-first traversal)</u>, также называемому <u>поиском в ширину (breadth-first search, BFS)</u>; он отражает идею "расширяться от центра к периферии слой за слоем".</p>
|
||||
<p>По своей сути обход по уровням относится к <u>обходу в ширину (breadth-first traversal)</u>, также называемому <u>поиском в ширину (breadth-first search, BFS)</u>. Он отражает идею «расширяться от центра к периферии слой за слоем».</p>
|
||||
<p><img alt="Обход двоичного дерева по уровням" class="animation-figure" src="../binary_tree_traversal.assets/binary_tree_bfs.png" /></p>
|
||||
<p align="center"> Рисунок 7-9 Обход двоичного дерева по уровням </p>
|
||||
|
||||
<h3 id="1">1. Код реализации<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>Обход в ширину обычно реализуется с помощью "очереди". Очередь подчиняется правилу "первым пришел - первым вышел", а обход в ширину подчиняется правилу "продвигаться по уровням", поэтому стоящая за ними идея согласована. Код реализации приведен ниже:</p>
|
||||
<p>Обход в ширину обычно реализуется с помощью «очереди». Очередь подчиняется правилу «первым пришел - первым вышел», а обход в ширину подчиняется правилу «продвигаться по уровням», поэтому стоящая за ними идея согласована. Код реализации приведен ниже:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:13"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" name="__tabbed_1" type="radio" /><input id="__tabbed_1_13" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Kotlin</label><label for="__tabbed_1_13">Ruby</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -4780,7 +4780,7 @@
|
||||
<li><strong>Пространственная сложность равна <span class="arithmatex">\(O(n)\)</span></strong> : в худшем случае, то есть для полной двоичной деревообразной структуры, до достижения самого нижнего уровня в очереди одновременно может находиться до <span class="arithmatex">\((n + 1) / 2\)</span> узлов, что требует <span class="arithmatex">\(O(n)\)</span> памяти.</li>
|
||||
</ul>
|
||||
<h2 id="722">7.2.2 Прямой, симметричный и обратный обходы<a class="headerlink" href="#722" title="Permanent link">¶</a></h2>
|
||||
<p>Соответственно, прямой, симметричный и обратный обходы относятся к <u>обходу в глубину (depth-first traversal)</u>, также называемому <u>поиском в глубину (depth-first search, DFS)</u>; он отражает идею "сначала идти до конца, затем возвращаться и продолжать".</p>
|
||||
<p>Соответственно, прямой, симметричный и обратный обходы относятся к <u>обходу в глубину (depth-first traversal)</u>, также называемому <u>поиском в глубину (depth-first search, DFS)</u>. Он отражает идею «сначала идти до конца, затем возвращаться и продолжать».</p>
|
||||
<p>На рисунке 7-10 показан принцип работы обхода двоичного дерева в глубину. <strong>Обход в глубину можно представить как обход всей двоичной структуры по внешнему контуру</strong> , и у каждого узла встречаются три позиции, соответствующие прямому, симметричному и обратному обходам.</p>
|
||||
<p><img alt="Прямой, симметричный и обратный обходы двоичного дерева поиска" class="animation-figure" src="../binary_tree_traversal.assets/binary_tree_dfs.png" /></p>
|
||||
<p align="center"> Рисунок 7-10 Прямой, симметричный и обратный обходы двоичного дерева поиска </p>
|
||||
@@ -5233,12 +5233,12 @@
|
||||
</details>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>Поиск в глубину можно реализовать и итеративно; заинтересованные читатели могут изучить это самостоятельно.</p>
|
||||
<p>Поиск в глубину можно реализовать и итеративно. Заинтересованные читатели могут изучить это самостоятельно.</p>
|
||||
</div>
|
||||
<p>На рисунках ниже показан рекурсивный процесс прямого обхода двоичного дерева. Его можно разделить на две противоположные части: "вход в рекурсию" и "возврат".</p>
|
||||
<p>На рисунке 7-11 показан рекурсивный процесс прямого обхода двоичного дерева. Его можно разделить на две противоположные части: «вход в рекурсию» и «возврат».</p>
|
||||
<ol>
|
||||
<li>"Вход в рекурсию" означает запуск нового вызова функции; в этом процессе программа переходит к следующему узлу.</li>
|
||||
<li>"Возврат" означает завершение вызова функции и возврат назад, то есть текущий узел уже полностью обработан.</li>
|
||||
<li>«Вход в рекурсию» означает запуск нового вызова функции. В этом процессе программа переходит к следующему узлу.</li>
|
||||
<li>«Возврат» означает завершение вызова функции и возврат назад, то есть текущий узел уже полностью обработан.</li>
|
||||
</ol>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="3:11"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><input id="__tabbed_3_11" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1"><1></label><label for="__tabbed_3_2"><2></label><label for="__tabbed_3_3"><3></label><label for="__tabbed_3_4"><4></label><label for="__tabbed_3_5"><5></label><label for="__tabbed_3_6"><6></label><label for="__tabbed_3_7"><7></label><label for="__tabbed_3_8"><8></label><label for="__tabbed_3_9"><9></label><label for="__tabbed_3_10"><10></label><label for="__tabbed_3_11"><11></label></div>
|
||||
<div class="tabbed-content">
|
||||
|
||||
@@ -4280,7 +4280,7 @@
|
||||
<div class="admonition abstract">
|
||||
<p class="admonition-title">Abstract</p>
|
||||
<p>Высокое дерево полно жизни: мощные корни, густая листва и раскидистые ветви.</p>
|
||||
<p>Оно наглядно показывает нам живую форму данных, построенную на принципе "разделяй и властвуй".</p>
|
||||
<p>Оно наглядно показывает нам живую форму данных, построенную на принципе «разделяй и властвуй».</p>
|
||||
</div>
|
||||
<h2 id="_1">Содержание главы<a class="headerlink" href="#_1" title="Permanent link">¶</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -4359,25 +4359,25 @@
|
||||
<h1 id="76">7.6 Краткие итоги<a class="headerlink" href="#76" title="Permanent link">¶</a></h1>
|
||||
<h3 id="1">1. Основные моменты<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<ul>
|
||||
<li>Двоичное дерево - это нелинейная структура данных, отражающая логику "разделяй и властвуй". Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам.</li>
|
||||
<li>Двоичное дерево - это нелинейная структура данных, отражающая логику «разделяй и властвуй». Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам.</li>
|
||||
<li>Для любого узла двоичного дерева дерево, образованное его левым (правым) дочерним узлом и всеми нижележащими узлами, называется левым (правым) поддеревом этого узла.</li>
|
||||
<li>К связанным с двоичным деревом терминам относятся корневой узел, листовой узел, уровень, степень, ребро, высота, глубина и так далее.</li>
|
||||
<li>Инициализация двоичного дерева, вставка узлов и удаление узлов аналогичны операциям со связным списком.</li>
|
||||
<li>К распространенным видам двоичного дерева относятся идеальное двоичное дерево, полное двоичное дерево, строгое двоичное дерево и сбалансированное двоичное дерево. Идеальное двоичное дерево - наиболее желательное состояние, а связный список - худший случай после вырождения.</li>
|
||||
<li>Двоичное дерево можно представить массивом: значения узлов и пустые позиции располагаются в порядке обхода по уровням, а связи между родителем и детьми реализуются через индексацию.</li>
|
||||
<li>Обход двоичного дерева по уровням является методом поиска в ширину; он отражает идею "расширяться от центра к периферии слой за слоем" и обычно реализуется через очередь.</li>
|
||||
<li>Прямой, симметричный и обратный обходы относятся к поиску в глубину; они отражают идею "сначала дойти до конца, затем вернуться и продолжить" и обычно реализуются рекурсивно.</li>
|
||||
<li>Двоичное дерево поиска - это эффективная структура данных для поиска элементов; его поиск, вставка и удаление имеют временную сложность <span class="arithmatex">\(O(\log n)\)</span> . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
<li>Обход двоичного дерева по уровням является методом поиска в ширину. Он отражает идею «расширяться от центра к периферии слой за слоем» и обычно реализуется через очередь.</li>
|
||||
<li>Прямой, симметричный и обратный обходы относятся к поиску в глубину. Они отражают идею «сначала дойти до конца, затем вернуться и продолжить» и обычно реализуются рекурсивно.</li>
|
||||
<li>Двоичное дерево поиска - это эффективная структура данных для поиска элементов. Его поиск, вставка и удаление имеют временную сложность <span class="arithmatex">\(O(\log n)\)</span> . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
<li>AVL-дерево, также называемое сбалансированным двоичным деревом поиска, с помощью вращений гарантирует, что после постоянных вставок и удалений узлов дерево остается сбалансированным.</li>
|
||||
<li>Вращения AVL-дерева включают правое вращение, левое вращение, сначала правое затем левое и сначала левое затем правое. После вставки или удаления узла AVL-дерево выполняет вращения снизу вверх, чтобы снова восстановить баланс.</li>
|
||||
</ul>
|
||||
<h3 id="2-q-a">2. Q & A<a class="headerlink" href="#2-q-a" title="Permanent link">¶</a></h3>
|
||||
<p><strong>Q</strong>: Для двоичного дерева, состоящего из одного узла, высота дерева и глубина корня обе равны <span class="arithmatex">\(0\)</span> ?</p>
|
||||
<p>Да, потому что высота и глубина обычно определяются как "число пройденных ребер".</p>
|
||||
<p><strong>Q</strong>: Вставка и удаление в двоичном дереве обычно выполняются в составе набора операций. Что именно означает этот "набор операций"? Можно ли понимать это как освобождение ресурсов у дочерних узлов ресурса?</p>
|
||||
<p>Да, потому что высота и глубина обычно определяются как «число пройденных ребер».</p>
|
||||
<p><strong>Q</strong>: Вставка и удаление в двоичном дереве обычно выполняются в составе набора операций. Что именно означает этот «набор операций»? Можно ли понимать это как освобождение ресурсов у дочерних узлов ресурса?</p>
|
||||
<p>Возьмем в качестве примера двоичное дерево поиска: операция удаления узла делится на три случая, и каждый из этих случаев требует нескольких последовательных шагов работы с узлами.</p>
|
||||
<p><strong>Q</strong>: Почему у DFS для двоичного дерева есть три порядка: прямой, симметричный и обратный? Для чего они нужны?</p>
|
||||
<p>Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы - это три способа обхода двоичного дерева, с помощью которых можно получить результаты в определенном порядке. Например, в двоичном дереве поиска, где соблюдается отношение <code>значение левого дочернего узла < значение корня < значение правого дочернего узла</code> , если обходить дерево с приоритетом "лево <span class="arithmatex">\(\rightarrow\)</span> корень <span class="arithmatex">\(\rightarrow\)</span> право", то получится упорядоченная последовательность узлов.</p>
|
||||
<p>Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы - это три способа обхода двоичного дерева, с помощью которых можно получить результаты в определенном порядке. Например, в двоичном дереве поиска, где соблюдается отношение <code>значение левого дочернего узла < значение корня < значение правого дочернего узла</code> , если обходить дерево с приоритетом «лево <span class="arithmatex">\(\rightarrow\)</span> корень <span class="arithmatex">\(\rightarrow\)</span> право», то получится упорядоченная последовательность узлов.</p>
|
||||
<p><strong>Q</strong>: Правое вращение работает с отношениями между <code>node</code> , <code>child</code> и <code>grand_child</code> . А связь между <code>node</code> и его исходным родителем разве не нужно поддерживать? После правого вращения она ведь не оборвется?</p>
|
||||
<p>На это нужно смотреть с точки зрения рекурсии. В правое вращение <code>right_rotate(root)</code> передается корень поддерева, а затем через <code>return child</code> возвращается корень этого поддерева уже после вращения. Соединение между новым корнем поддерева и его родителем восстанавливается после возврата функции и не входит в обязанности самой операции правого вращения.</p>
|
||||
<p><strong>Q</strong>: В C++ функции делятся на <code>private</code> и <code>public</code> . Какая логика стоит за этим? Почему <code>height()</code> и <code>updateHeight()</code> помещают в разные области видимости?</p>
|
||||
|
||||
Reference in New Issue
Block a user