mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-19 23:10:58 +00:00
deploy
This commit is contained in:
@@ -3595,11 +3595,11 @@
|
||||
<li>在从顶至底堆化的过程中,每个节点最多堆化到叶节点,因此最大迭代次数为二叉树高度 <span class="arithmatex">\(O(\log n)\)</span> 。</li>
|
||||
</ul>
|
||||
<p>将上述两者相乘,可得到建堆过程的时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> 。<strong>然而,这个估算结果并不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的特性</strong>。</p>
|
||||
<p>接下来我们来进行更为详细的计算。为了减小计算难度,我们假设树是一个“完美二叉树”,该假设不会影响计算结果的正确性。设二叉树(即堆)节点数量为 <span class="arithmatex">\(n\)</span> ,树高度为 <span class="arithmatex">\(h\)</span> 。上文提到,<strong>节点堆化最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”</strong>。</p>
|
||||
<p>接下来我们来进行更为详细的计算。为了减小计算难度,我们假设树是一个“完美二叉树”,该假设不会影响计算结果的正确性。设二叉树(即堆)节点数量为 <span class="arithmatex">\(n\)</span> ,树高度为 <span class="arithmatex">\(h\)</span> 。</p>
|
||||
<p><img alt="完美二叉树的各层节点数量" src="../build_heap.assets/heapify_operations_count.png" /></p>
|
||||
<p align="center"> 图:完美二叉树的各层节点数量 </p>
|
||||
|
||||
<p>因此,我们可以将各层的“节点数量 <span class="arithmatex">\(\times\)</span> 节点高度”求和,<strong>从而得到所有节点的堆化迭代次数的总和</strong>。</p>
|
||||
<p>如上图所示,<strong>节点“从顶至底堆化”的最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”</strong>。因此,我们可以将各层的“节点数量 <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>
|
||||
|
||||
@@ -3494,7 +3494,7 @@
|
||||
|
||||
|
||||
<h1 id="81">8.1 堆<a class="headerlink" href="#81" title="Permanent link">¶</a></h1>
|
||||
<p>「堆 heap」是一种满足特定条件的完全二叉树,可分为两种类型:</p>
|
||||
<p>「堆 heap」是一种满足特定条件的完全二叉树,主要可分为下图所示的两种类型:</p>
|
||||
<ul>
|
||||
<li>「大顶堆 max heap」:任意节点的值 <span class="arithmatex">\(\geq\)</span> 其子节点的值。</li>
|
||||
<li>「小顶堆 min heap」:任意节点的值 <span class="arithmatex">\(\leq\)</span> 其子节点的值。</li>
|
||||
@@ -3816,7 +3816,7 @@
|
||||
<h3 id="1">1. 堆的存储与表示<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>我们在二叉树章节中学习到,完全二叉树非常适合用数组来表示。由于堆正是一种完全二叉树,<strong>我们将采用数组来存储堆</strong>。</p>
|
||||
<p>当使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置。<strong>节点指针通过索引映射公式来实现</strong>。</p>
|
||||
<p>具体而言,给定索引 <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>如下图所示,给定索引 <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="堆的表示与存储" src="../heap.assets/representation_of_heap.png" /></p>
|
||||
<p align="center"> 图:堆的表示与存储 </p>
|
||||
|
||||
@@ -4119,7 +4119,7 @@
|
||||
</div>
|
||||
<h3 id="3">3. 元素入堆<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||
<p>给定元素 <code>val</code> ,我们首先将其添加到堆底。添加之后,由于 val 可能大于堆中其他元素,堆的成立条件可能已被破坏。因此,<strong>需要修复从插入节点到根节点的路径上的各个节点</strong>,这个操作被称为「堆化 heapify」。</p>
|
||||
<p>考虑从入堆节点开始,<strong>从底至顶执行堆化</strong>。具体来说,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。</p>
|
||||
<p>考虑从入堆节点开始,<strong>从底至顶执行堆化</strong>。如下图所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。</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">
|
||||
@@ -4477,7 +4477,7 @@
|
||||
<li>交换完成后,将堆底从列表中删除(注意,由于已经交换,实际上删除的是原来的堆顶元素)。</li>
|
||||
<li>从根节点开始,<strong>从顶至底执行堆化</strong>。</li>
|
||||
</ol>
|
||||
<p>顾名思义,<strong>从顶至底堆化的操作方向与从底至顶堆化相反</strong>,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换;然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。</p>
|
||||
<p>如下图所示,<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">
|
||||
|
||||
@@ -3432,8 +3432,8 @@
|
||||
</div>
|
||||
<p>对于该问题,我们先介绍两种思路比较直接的解法,再介绍效率更高的堆解法。</p>
|
||||
<h2 id="831">8.3.1 方法一:遍历选择<a class="headerlink" href="#831" title="Permanent link">¶</a></h2>
|
||||
<p>我们可以进行 <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>我们可以进行下图所示的 <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 个元素" src="../top_k.assets/top_k_traversal.png" /></p>
|
||||
<p align="center"> 图:遍历寻找最大的 k 个元素 </p>
|
||||
|
||||
@@ -3442,13 +3442,13 @@
|
||||
<p>当 <span class="arithmatex">\(k = n\)</span> 时,我们可以得到从大到小的序列,等价于“选择排序”算法。</p>
|
||||
</div>
|
||||
<h2 id="832">8.3.2 方法二:排序<a class="headerlink" href="#832" title="Permanent link">¶</a></h2>
|
||||
<p>我们可以对数组 <code>nums</code> 进行排序,并返回最右边的 <span class="arithmatex">\(k\)</span> 个元素,时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> 。</p>
|
||||
<p>如下图所示,我们可以先对数组 <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 个元素" src="../top_k.assets/top_k_sorting.png" /></p>
|
||||
<p align="center"> 图:排序寻找最大的 k 个元素 </p>
|
||||
|
||||
<h2 id="833">8.3.3 方法三:堆<a class="headerlink" href="#833" title="Permanent link">¶</a></h2>
|
||||
<p>我们可以基于堆更加高效地解决 Top-K 问题,流程如下:</p>
|
||||
<p>我们可以基于堆更加高效地解决 Top-K 问题,流程如下图所示。</p>
|
||||
<ol>
|
||||
<li>初始化一个小顶堆,其堆顶元素最小。</li>
|
||||
<li>先将数组的前 <span class="arithmatex">\(k\)</span> 个元素依次入堆。</li>
|
||||
|
||||
Reference in New Issue
Block a user