mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-17 14:10:57 +00:00
deploy
This commit is contained in:
@@ -4383,11 +4383,11 @@
|
||||
<h2 id="821">8.2.1 Реализация через операцию добавления в кучу<a class="headerlink" href="#821" title="Permanent link">¶</a></h2>
|
||||
<p>Сначала мы создаем пустую кучу, затем обходим список и для каждого элемента по очереди выполняем операцию добавления в кучу: сначала помещаем элемент в хвост кучи, а затем выполняем для него упорядочивание снизу вверх.</p>
|
||||
<p>Каждый раз, когда элемент добавляется в кучу, ее длина увеличивается на единицу. Поскольку узлы последовательно добавляются в двоичное дерево сверху вниз, куча строится сверху вниз.</p>
|
||||
<p>Пусть число элементов равно <span class="arithmatex">\(n\)</span> ; так как каждая операция добавления требует <span class="arithmatex">\(O(\log{n})\)</span> времени, временная сложность такого построения кучи составляет <span class="arithmatex">\(O(n \log n)\)</span> .</p>
|
||||
<p>Пусть число элементов равно <span class="arithmatex">\(n\)</span>. Так как каждая операция добавления требует <span class="arithmatex">\(O(\log{n})\)</span> времени, временная сложность такого построения кучи составляет <span class="arithmatex">\(O(n \log n)\)</span> .</p>
|
||||
<h2 id="822">8.2.2 Реализация через обход и упорядочивание<a class="headerlink" href="#822" title="Permanent link">¶</a></h2>
|
||||
<p>На самом деле можно реализовать и более эффективный способ построения кучи, который состоит из двух шагов.</p>
|
||||
<ol>
|
||||
<li>Без изменений добавить все элементы списка в кучу; в этот момент свойства кучи еще не выполняются.</li>
|
||||
<li>Без изменений добавить все элементы списка в кучу. В этот момент свойства кучи еще не выполняются.</li>
|
||||
<li>Обойти кучу в обратном порядке, то есть в порядке, обратном обходу по уровням, и по очереди выполнить упорядочивание сверху вниз для каждого нелистового узла.</li>
|
||||
</ol>
|
||||
<p><strong>После того как некоторый узел был упорядочен, поддерево с этим узлом в качестве корня становится корректной подкучей</strong>. А поскольку обход выполняется в обратном порядке, куча строится снизу вверх.</p>
|
||||
@@ -4678,11 +4678,11 @@
|
||||
<li>В процессе упорядочивания сверху вниз каждый узел в худшем случае может просеяться до листа, поэтому максимальное число итераций равно высоте двоичного дерева <span class="arithmatex">\(\log n\)</span> .</li>
|
||||
</ul>
|
||||
<p>Перемножив эти два значения, можно получить временную сложность построения кучи <span class="arithmatex">\(O(n \log n)\)</span> . <strong>Но эта оценка неточна, потому что мы не учли свойство двоичного дерева: на нижних уровнях узлов гораздо больше, чем на верхних</strong>.</p>
|
||||
<p>Далее выполним более точный расчет. Чтобы упростить вычисления, предположим, что дано "идеальное двоичное дерево" высоты <span class="arithmatex">\(h\)</span> с числом узлов <span class="arithmatex">\(n\)</span> ; это предположение не повлияет на корректность результата.</p>
|
||||
<p>Далее выполним более точный расчет. Чтобы упростить вычисления, предположим, что дано «идеальное двоичное дерево» высоты <span class="arithmatex">\(h\)</span> с числом узлов <span class="arithmatex">\(n\)</span>. Это предположение не повлияет на корректность результата.</p>
|
||||
<p><img alt="Число узлов на каждом уровне идеального двоичного дерева" class="animation-figure" src="../build_heap.assets/heapify_operations_count.png" /></p>
|
||||
<p align="center"> Рисунок 8-5 Число узлов на каждом уровне идеального двоичного дерева </p>
|
||||
|
||||
<p>Как показано на рисунке 8-5, максимальное число итераций упорядочивания сверху вниз для некоторого узла равно расстоянию от этого узла до листового узла, а это расстояние как раз и есть высота узла. Поэтому мы можем просуммировать для каждого уровня выражение "число узлов <span class="arithmatex">\(\times\)</span> высота узла" и <strong>получить суммарное число итераций упорядочивания для всех узлов</strong>.</p>
|
||||
<p>Как показано на рисунке 8-5, максимальное число итераций упорядочивания сверху вниз для некоторого узла равно расстоянию от этого узла до листового узла, а это расстояние как раз и есть высота узла. Поэтому мы можем просуммировать для каждого уровня выражение «число узлов <span class="arithmatex">\(\times\)</span> высота узла» и <strong>получить суммарное число итераций упорядочивания для всех узлов</strong>.</p>
|
||||
<div class="arithmatex">\[
|
||||
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1
|
||||
\]</div>
|
||||
|
||||
@@ -4538,7 +4538,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<p>В реальных приложениях мы можем напрямую использовать классы кучи, предоставляемые языком программирования, или классы очереди с приоритетом.</p>
|
||||
<p>Подобно сортировкам "по возрастанию" и "по убыванию", мы можем переключаться между "минимальной кучей" и "максимальной кучей", изменяя <code>flag</code> или модифицируя <code>Comparator</code> . Код приведен ниже:</p>
|
||||
<p>Подобно сортировкам «по возрастанию» и «по убыванию», мы можем переключаться между «минимальной кучей» и «максимальной кучей», изменяя <code>flag</code> или модифицируя <code>Comparator</code> . Код приведен ниже:</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">
|
||||
@@ -4897,7 +4897,7 @@
|
||||
<h2 id="812">8.1.2 Реализация кучи<a class="headerlink" href="#812" title="Permanent link">¶</a></h2>
|
||||
<p>Ниже реализуется максимальная куча. Чтобы преобразовать ее в минимальную кучу, достаточно инвертировать всю логику сравнений по величине, например заменить <span class="arithmatex">\(\geq\)</span> на <span class="arithmatex">\(\leq\)</span> . Заинтересованные читатели могут попробовать реализовать это самостоятельно.</p>
|
||||
<h3 id="1">1. Хранение и представление кучи<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>В разделе "Двоичные деревья" мы уже говорили, что полное двоичное дерево очень удобно представлять массивом. Поскольку куча как раз и является полным двоичным деревом, <strong>для хранения кучи мы также будем использовать массив</strong>.</p>
|
||||
<p>В разделе «Двоичные деревья» мы уже говорили, что полное двоичное дерево очень удобно представлять массивом. Поскольку куча как раз и является полным двоичным деревом, <strong>для хранения кучи мы также будем использовать массив</strong>.</p>
|
||||
<p>Когда двоичное дерево представляется массивом, элементы массива соответствуют значениям узлов, а индексы - положениям этих узлов в двоичном дереве. <strong>Указатели на узлы реализуются через формулы отображения индексов</strong>.</p>
|
||||
<p>Как показано на рисунке 8-2, для заданного индекса <span class="arithmatex">\(i\)</span> индекс левого дочернего узла равен <span class="arithmatex">\(2i + 1\)</span> , правого дочернего узла - <span class="arithmatex">\(2i + 2\)</span> , а родительского узла - <span class="arithmatex">\((i - 1) / 2\)</span> с округлением вниз. Если индекс выходит за допустимые границы, это означает пустой узел или отсутствие узла.</p>
|
||||
<p><img alt="Представление и хранение кучи" class="animation-figure" src="../heap.assets/representation_of_heap.png" /></p>
|
||||
@@ -5229,8 +5229,8 @@
|
||||
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%BA%D1%83%D1%87%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%20%D0%B1%D0%B5%D0%B7%20%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%80%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%D0%9E%D0%BA%D1%80%D1%83%D0%B3%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%B7%20%D0%BF%D1%80%D0%B8%20%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B8%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%B0%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BA%D1%83%D1%87%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%3A%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%83%D0%B6%D0%B5%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D0%BA%D0%BE%D1%80%D1%80%D0%B5%D0%BA%D1%82%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B5%D0%B9%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D1%81%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%8B%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%20%3D%20%7Bpeek%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=7&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div></p>
|
||||
</details>
|
||||
<h3 id="3">3. Добавление элемента в кучу<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||
<p>Пусть дан элемент <code>val</code> . Сначала мы помещаем его в основание кучи. После добавления свойства кучи могут нарушиться, потому что <code>val</code> может оказаться больше, чем другие элементы в куче. <strong>Поэтому необходимо восстановить порядок на пути от вставленного узла к корню</strong> ; эта операция называется упорядочиванием кучи.</p>
|
||||
<p>Рассмотрим ситуацию, когда упорядочивание выполняется <strong>снизу вверх</strong>, начиная от только что вставленного узла. Как показано на рисунках ниже, мы сравниваем значение вставленного узла со значением его родителя; если вставленный узел больше, то меняем их местами. Затем продолжаем выполнять ту же операцию и последовательно восстанавливать корректность всех узлов по пути снизу вверх, пока не выйдем за корень или не встретим узел, для которого обмен не требуется.</p>
|
||||
<p>Пусть дан элемент <code>val</code> . Сначала мы помещаем его в основание кучи. После добавления свойства кучи могут нарушиться, потому что <code>val</code> может оказаться больше, чем другие элементы в куче. <strong>Поэтому необходимо восстановить порядок на пути от вставленного узла к корню</strong>. Эта операция называется упорядочиванием кучи.</p>
|
||||
<p>Рассмотрим ситуацию, когда упорядочивание выполняется <strong>снизу вверх</strong>, начиная от только что вставленного узла. Как показано на рисунке 8-3, мы сравниваем значение вставленного узла со значением его родителя. Если вставленный узел больше, то меняем их местами. Затем продолжаем выполнять ту же операцию и последовательно восстанавливать корректность всех узлов по пути снизу вверх, пока не выйдем за корень или не встретим узел, для которого обмен не требуется.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="4:9"><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" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" 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><label for="__tabbed_4_5"><5></label><label for="__tabbed_4_6"><6></label><label for="__tabbed_4_7"><7></label><label for="__tabbed_4_8"><8></label><label for="__tabbed_4_9"><9></label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5618,7 +5618,7 @@
|
||||
<li>После обмена удалить основание кучи из списка. Стоит отметить, что, поскольку обмен уже выполнен, фактически удаляется исходный элемент вершины кучи.</li>
|
||||
<li>Начиная от корневого узла, <strong>выполнить упорядочивание кучи сверху вниз</strong>.</li>
|
||||
</ol>
|
||||
<p>Как показано на рисунках ниже, <strong>направление операции упорядочивания кучи сверху вниз противоположно операции упорядочивания кучи снизу вверх</strong>. Мы сравниваем значение корневого узла со значениями двух дочерних узлов, выбираем больший дочерний узел и меняем его местами с корневым узлом. Затем циклически повторяем ту же операцию, пока не выйдем за листовой узел или не встретим узел, который уже не требует обмена.</p>
|
||||
<p>Как показано на рисунке 8-4, <strong>направление операции упорядочивания кучи сверху вниз противоположно операции упорядочивания кучи снизу вверх</strong>. Мы сравниваем значение корневого узла со значениями двух дочерних узлов, выбираем больший дочерний узел и меняем его местами с корневым узлом. Затем циклически повторяем ту же операцию, пока не выйдем за листовой узел или не встретим узел, который уже не требует обмена.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="6:10"><input checked="checked" id="__tabbed_6_1" name="__tabbed_6" type="radio" /><input id="__tabbed_6_2" name="__tabbed_6" type="radio" /><input id="__tabbed_6_3" name="__tabbed_6" type="radio" /><input id="__tabbed_6_4" name="__tabbed_6" type="radio" /><input id="__tabbed_6_5" name="__tabbed_6" type="radio" /><input id="__tabbed_6_6" name="__tabbed_6" type="radio" /><input id="__tabbed_6_7" name="__tabbed_6" type="radio" /><input id="__tabbed_6_8" name="__tabbed_6" type="radio" /><input id="__tabbed_6_9" name="__tabbed_6" type="radio" /><input id="__tabbed_6_10" name="__tabbed_6" type="radio" /><div class="tabbed-labels"><label for="__tabbed_6_1"><1></label><label for="__tabbed_6_2"><2></label><label for="__tabbed_6_3"><3></label><label for="__tabbed_6_4"><4></label><label for="__tabbed_6_5"><5></label><label for="__tabbed_6_6"><6></label><label for="__tabbed_6_7"><7></label><label for="__tabbed_6_8"><8></label><label for="__tabbed_6_9"><9></label><label for="__tabbed_6_10"><10></label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -6149,8 +6149,8 @@
|
||||
</details>
|
||||
<h2 id="813">8.1.3 Типичные применения кучи<a class="headerlink" href="#813" title="Permanent link">¶</a></h2>
|
||||
<ul>
|
||||
<li><strong>Очередь с приоритетом</strong>: куча обычно является предпочтительной структурой данных для реализации очереди с приоритетом; добавление и извлечение элементов имеют временную сложность <span class="arithmatex">\(O(\log n)\)</span> , а построение кучи - <span class="arithmatex">\(O(n)\)</span> , и все эти операции выполняются очень эффективно.</li>
|
||||
<li><strong>Пирамидальная сортировка</strong>: для заданного набора данных можно построить кучу, а затем непрерывно извлекать из нее элементы, получая отсортированные данные. Однако на практике мы обычно используем более изящную реализацию пирамидальной сортировки; подробности см. в разделе "Пирамидальная сортировка".</li>
|
||||
<li><strong>Очередь с приоритетом</strong>: куча обычно является предпочтительной структурой данных для реализации очереди с приоритетом. Добавление и извлечение элементов имеют временную сложность <span class="arithmatex">\(O(\log n)\)</span> , а построение кучи - <span class="arithmatex">\(O(n)\)</span> , и все эти операции выполняются очень эффективно.</li>
|
||||
<li><strong>Пирамидальная сортировка</strong>: для заданного набора данных можно построить кучу, а затем непрерывно извлекать из нее элементы, получая отсортированные данные. Однако на практике мы обычно используем более изящную реализацию пирамидальной сортировки. Подробности см. в разделе «Пирамидальная сортировка».</li>
|
||||
<li><strong>Получение наибольших <span class="arithmatex">\(k\)</span> элементов</strong>: это классическая алгоритмическая задача и одновременно типичное применение кучи. Например, выбор 10 самых горячих новостей для списка популярных тем или выбор 10 самых продаваемых товаров.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -4360,7 +4360,7 @@
|
||||
<h3 id="1">1. Ключевые выводы<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<ul>
|
||||
<li>Куча представляет собой полное двоичное дерево и делится на максимальную кучу и минимальную кучу. Элемент на вершине максимальной (минимальной) кучи является наибольшим (наименьшим).</li>
|
||||
<li>Очередь с приоритетом определяется как очередь, элементы которой извлекаются в соответствии с приоритетом; обычно ее реализуют с помощью кучи.</li>
|
||||
<li>Очередь с приоритетом определяется как очередь, элементы которой извлекаются в соответствии с приоритетом. Обычно ее реализуют с помощью кучи.</li>
|
||||
<li>К основным операциям кучи и их временным сложностям относятся: добавление элемента в кучу <span class="arithmatex">\(O(\log n)\)</span> , извлечение элемента с вершины кучи <span class="arithmatex">\(O(\log n)\)</span> и доступ к вершине кучи <span class="arithmatex">\(O(1)\)</span> .</li>
|
||||
<li>Полное двоичное дерево очень удобно представлять массивом, поэтому кучу обычно тоже хранят в массиве.</li>
|
||||
<li>Операция упорядочивания кучи используется для поддержания свойств кучи и применяется как при добавлении элемента, так и при извлечении элемента.</li>
|
||||
@@ -4368,7 +4368,7 @@
|
||||
<li>Top-k - это классическая алгоритмическая задача, которую можно эффективно решать с помощью кучи за <span class="arithmatex">\(O(n \log k)\)</span> .</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>: Является ли "куча" как структура данных тем же самым понятием, что и "куча" в управлении памятью?</p>
|
||||
<p><strong>Q</strong>: Является ли «куча» как структура данных тем же самым понятием, что и «куча» в управлении памятью?</p>
|
||||
<p>Это не одно и то же, просто у них случайно совпало название. Куча в памяти компьютерной системы является частью динамического распределения памяти: во время выполнения программы она используется для хранения данных. Программа может запросить определенный объем памяти в куче для хранения сложных структур, таких как объекты и массивы. Когда эти данные больше не нужны, память нужно освободить, чтобы не допустить утечек. По сравнению со стековой памятью управление памятью в куче требует большей осторожности, а неправильное использование может привести к утечкам памяти и проблемам с указателями.</p>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
@@ -4385,23 +4385,23 @@
|
||||
</div>
|
||||
<p>Для этой задачи мы сначала покажем два относительно прямолинейных способа решения, а затем более эффективный способ на основе кучи.</p>
|
||||
<h2 id="831-1">8.3.1 Метод 1: выбор через обход<a class="headerlink" href="#831-1" title="Permanent link">¶</a></h2>
|
||||
<p>Как показано на рисунке 8-6, можно выполнить <span class="arithmatex">\(k\)</span> проходов по массиву и на каждом проходе извлекать соответственно <span class="arithmatex">\(1\)</span>-й, <span class="arithmatex">\(2\)</span>-й, <span class="arithmatex">\(\dots\)</span> , <span class="arithmatex">\(k\)</span>-й по величине элемент; временная сложность такого подхода равна <span class="arithmatex">\(O(nk)\)</span> .</p>
|
||||
<p>Как показано на рисунке 8-6, можно выполнить <span class="arithmatex">\(k\)</span> проходов по массиву и на каждом проходе извлекать соответственно <span class="arithmatex">\(1\)</span>-й, <span class="arithmatex">\(2\)</span>-й, <span class="arithmatex">\(\dots\)</span> , <span class="arithmatex">\(k\)</span>-й по величине элемент. Временная сложность такого подхода равна <span class="arithmatex">\(O(nk)\)</span> .</p>
|
||||
<p>Этот метод подходит только для случая <span class="arithmatex">\(k \ll n\)</span> , потому что когда <span class="arithmatex">\(k\)</span> приближается к <span class="arithmatex">\(n\)</span> , его временная сложность стремится к <span class="arithmatex">\(O(n^2)\)</span> , а это уже очень затратно.</p>
|
||||
<p><img alt="Поиск наибольших k элементов через обход" class="animation-figure" src="../top_k.assets/top_k_traversal.png" /></p>
|
||||
<p align="center"> Рисунок 8-6 Поиск наибольших k элементов через обход </p>
|
||||
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>Когда <span class="arithmatex">\(k = n\)</span> , мы получаем полную упорядоченную последовательность, и в этот момент задача становится эквивалентной алгоритму "сортировка выбором".</p>
|
||||
<p>Когда <span class="arithmatex">\(k = n\)</span> , мы получаем полную упорядоченную последовательность, и в этот момент задача становится эквивалентной алгоритму «сортировка выбором».</p>
|
||||
</div>
|
||||
<h2 id="832-2">8.3.2 Метод 2: сортировка<a class="headerlink" href="#832-2" title="Permanent link">¶</a></h2>
|
||||
<p>Как показано на рисунке 8-7, можно сначала отсортировать массив <code>nums</code> , а затем вернуть его крайние правые <span class="arithmatex">\(k\)</span> элементов; временная сложность такого метода равна <span class="arithmatex">\(O(n \log n)\)</span> .</p>
|
||||
<p>Как показано на рисунке 8-7, можно сначала отсортировать массив <code>nums</code> , а затем вернуть его крайние правые <span class="arithmatex">\(k\)</span> элементов. Временная сложность такого метода равна <span class="arithmatex">\(O(n \log n)\)</span> .</p>
|
||||
<p>Очевидно, что этот способ делает слишком много, потому что нам нужно только найти наибольшие <span class="arithmatex">\(k\)</span> элементов, а сортировать остальные элементы совсем не обязательно.</p>
|
||||
<p><img alt="Поиск наибольших k элементов через сортировку" class="animation-figure" src="../top_k.assets/top_k_sorting.png" /></p>
|
||||
<p align="center"> Рисунок 8-7 Поиск наибольших k элементов через сортировку </p>
|
||||
|
||||
<h2 id="833-3">8.3.3 Метод 3: куча<a class="headerlink" href="#833-3" title="Permanent link">¶</a></h2>
|
||||
<p>Задачу Top-k можно решить гораздо эффективнее с помощью кучи, как показано на рисунках ниже.</p>
|
||||
<p>Задачу Top-k можно решить гораздо эффективнее с помощью кучи, как показано на рисунке 8-8.</p>
|
||||
<ol>
|
||||
<li>Инициализировать минимальную кучу, у которой вершина содержит наименьший элемент.</li>
|
||||
<li>Сначала по очереди поместить в кучу первые <span class="arithmatex">\(k\)</span> элементов массива.</li>
|
||||
@@ -4812,7 +4812,7 @@
|
||||
<p><div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20k%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B5%20k%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20k%2B1%2C%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%D0%B9%20k%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D0%B8%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
|
||||
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20k%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B5%20k%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20k%2B1%2C%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%D0%B9%20k%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D0%B8%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div></p>
|
||||
</details>
|
||||
<p>Всего выполняется <span class="arithmatex">\(n\)</span> операций добавления и извлечения из кучи, а максимальная длина кучи равна <span class="arithmatex">\(k\)</span> , поэтому временная сложность равна <span class="arithmatex">\(O(n \log k)\)</span> . Этот метод очень эффективен: когда <span class="arithmatex">\(k\)</span> мало, временная сложность стремится к <span class="arithmatex">\(O(n)\)</span> ; когда <span class="arithmatex">\(k\)</span> велико, она все равно не превышает <span class="arithmatex">\(O(n \log n)\)</span> .</p>
|
||||
<p>Всего выполняется <span class="arithmatex">\(n\)</span> операций добавления и извлечения из кучи, а максимальная длина кучи равна <span class="arithmatex">\(k\)</span> , поэтому временная сложность равна <span class="arithmatex">\(O(n \log k)\)</span> . Этот метод очень эффективен: когда <span class="arithmatex">\(k\)</span> мало, временная сложность стремится к <span class="arithmatex">\(O(n)\)</span>. Когда <span class="arithmatex">\(k\)</span> велико, она все равно не превышает <span class="arithmatex">\(O(n \log n)\)</span> .</p>
|
||||
<p>Кроме того, этот метод подходит и для сценариев с динамическим потоком данных. При непрерывном поступлении новых данных мы можем продолжать поддерживать содержимое кучи, тем самым динамически обновляя наибольшие <span class="arithmatex">\(k\)</span> элементов.</p>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
Reference in New Issue
Block a user