mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-12 15:36:05 +00:00
deploy
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user