This commit is contained in:
krahets
2023-03-23 18:57:04 +08:00
parent 129c80e04b
commit 3222b84a5b
28 changed files with 253 additions and 265 deletions

View File

@ -21,7 +21,7 @@
<title>Bucket sort - Hello 算法</title>
<title>桶排序 - Hello 算法</title>
@ -109,7 +109,7 @@
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Bucket sort
桶排序
</span>
</div>
@ -1607,21 +1607,8 @@
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
目录
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#_1" class="md-nav__link">
拓展到桶排序
</a>
</li>
</ul>
</nav>
</div>
@ -1644,19 +1631,21 @@
<h1>Bucket sort</h1>
<h2 id="_1">拓展到桶排序<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h2>
<p>如果我们把上述 <code>bucket</code> 中的每个索引想象成一个桶,那么可以将计数排序理解为把 <span class="arithmatex">\(n\)</span> 个元素分配到对应的桶中,再根据桶与桶之间天然的有序性来实现排序。</p>
<p>以上解读便是「桶排序 Bucket Sort」的核心思想。具体地桶排序考虑将 <span class="arithmatex">\(n\)</span> 个元素根据大小范围均匀地分配到 <span class="arithmatex">\(k\)</span> 个桶中,由于桶之间是有序的,<strong>因此仅需在每个桶内部执行排序</strong>,最终按照桶之间的大小关系将元素依次排列,即可得到排序结果。</p>
<p>假设使用「快速排序」来排序各个桶内的元素,每个桶内元素数量为 <span class="arithmatex">\(\frac{n}{k}\)</span> ,则排序单个桶使用 <span class="arithmatex">\(O(\frac{n}{k} \log\frac{n}{k})\)</span> 时间,排序所有桶使用 <span class="arithmatex">\(O(n \log\frac{n}{k})\)</span> 时间。<strong>当桶数量 <span class="arithmatex">\(k\)</span> 接近 <span class="arithmatex">\(n\)</span> 时,时间复杂度则趋向于 <span class="arithmatex">\(O(n)\)</span></strong> </p>
<h1 id="_1">桶排序<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h1>
<p>「桶排序 Bucket Sort」考虑设置 <span class="arithmatex">\(k\)</span> 个桶,并将 <span class="arithmatex">\(n\)</span> 个元素根据大小分配到 <span class="arithmatex">\(k\)</span> 个桶中,<strong>并在每个桶内部分别执行排序</strong>,由于桶之间的大小关系的确定的,因此最后按照桶之间的顺序将元素依次展开即可。</p>
<p>假设元素平均分布在各个桶内,则每个桶内元素数量为 <span class="arithmatex">\(\frac{n}{k}\)</span> ;如果使用「快速排序」来实现桶内排序,则排序单个桶使用 <span class="arithmatex">\(O(\frac{n}{k} \log\frac{n}{k})\)</span> 时间,排序所有桶使用 <span class="arithmatex">\(O(n \log\frac{n}{k})\)</span> 时间。<strong>当桶数量 <span class="arithmatex">\(k\)</span> 比较大时,时间复杂度则趋向于 <span class="arithmatex">\(O(n)\)</span></strong></p>
<div class="admonition note 计数排序与桶排序的关系">
<p class="admonition-title">Note</p>
<p><strong>计数排序可以看作是桶排序的一种特例</strong>。我们可以把计数排序中 <code>counter</code> 的每个索引想象成一个桶,将统计数量的过程想象成把 <span class="arithmatex">\(n\)</span> 个元素分配到对应的桶中,再根据桶之间的有序性输出结果,从而实现排序</p>
</div>
<p>(图)</p>
<p>理论上桶排序的时间复杂度是 <span class="arithmatex">\(O(n)\)</span> <strong>但前提是需要将元素均匀分配到各个桶中</strong>,而这是不太容易做到。假设我们要把淘宝<span class="arithmatex">\(100\)</span> 万件商品根据价格范围平均分配到 <span class="arithmatex">\(100\)</span> 个桶中,由于商品价格不是均匀分布的,<span class="arithmatex">\(1\)</span> ~ <span class="arithmatex">\(100\)</span> 元的商品非常多、<span class="arithmatex">\(1\)</span> 万元以上的商品非常少等,因此难以简单地设定各个桶的价格分界线。解决方案有</p>
<p>理论上桶排序的时间复杂度是 <span class="arithmatex">\(O(n)\)</span> <strong>但前提是需要将元素均匀分配到各个桶中</strong>,而这并不容易做到。假设要把淘宝的 <span class="arithmatex">\(100\)</span> 万件商品根据价格范围平均分配到 <span class="arithmatex">\(100\)</span> 个桶中,商品价格不是均匀分布的,<span class="arithmatex">\(100\)</span> 元以下的商品非常多、<span class="arithmatex">\(1000\)</span>以上的商品非常少等。如果我们将价格区间平均划分为 <span class="arithmatex">\(100\)</span> 份,那么各个桶内的商品数量差距会非常大。为了实现平均分配,我们一般这样做</p>
<ul>
<li>初步设置一个分界线,将元素分配完后,<strong>把元素较多的桶继续划分为多个桶</strong>,直至每个桶内元素数量合理为止;该做法一般使用递归实现</li>
<li>如果我们提前知道商品价格的概率分布,<strong>则可以根据已知分布来设置每个桶的价格分界线</strong>;值得说明的是,数据分布不一定需要 case-by-case 地统计,有时可以采用一些常见分布来近似,例如自然界的正态分布;</li>
<li>粗略设置分界线,将元素分配完后,<strong>把元素较多的桶继续划分为多个桶</strong>,直至所有桶内元素数量合理为止;该做法本质上是一个递归树</li>
<li>如果我们提前知道商品价格的概率分布,<strong>则可以根据已知分布来设置每个桶的价格分界线</strong>;值得说明的是,数据分布不一定需要特意统计,也可以根据数据特点采用某种常见概率模型来近似,例如自然界的正态分布</li>
</ul>
<p>(图)</p>
<p>另外,排序桶内元素需要选择一种合适的排序算法,比如快速排序。</p>