This commit is contained in:
krahets
2026-04-14 18:06:19 +08:00
parent 17b2a0b630
commit cf0747ba3e
131 changed files with 604 additions and 609 deletions
+5 -5
View File
@@ -4385,23 +4385,23 @@
</div>
<p>Для этой задачи мы сначала покажем два относительно прямолинейных способа решения, а затем более эффективный способ на основе кучи.</p>
<h2 id="831-1">8.3.1 &nbsp; Метод 1: выбор через обход<a class="headerlink" href="#831-1" title="Permanent link">&para;</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 &nbsp; Поиск наибольших 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 &nbsp; Метод 2: сортировка<a class="headerlink" href="#832-2" title="Permanent link">&para;</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 &nbsp; Поиск наибольших k элементов через сортировку </p>
<h2 id="833-3">8.3.3 &nbsp; Метод 3: куча<a class="headerlink" href="#833-3" title="Permanent link">&para;</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">Во весь экран &gt;</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 -->