This commit is contained in:
krahets
2023-05-24 00:32:47 +08:00
parent 9a0252f484
commit c6937e63b7
80 changed files with 4101 additions and 905 deletions

View File

@ -25,7 +25,7 @@
<title>11.6.   桶排序 - Hello 算法</title>
<title>11.7.   桶排序 - Hello 算法</title>
@ -79,7 +79,7 @@
<div data-md-component="skip">
<a href="#116" class="md-skip">
<a href="#117" class="md-skip">
跳转至
</a>
@ -113,7 +113,7 @@
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
11.6. &nbsp; 桶排序
11.7. &nbsp; 桶排序
</span>
</div>
@ -1409,6 +1409,8 @@
@ -1445,9 +1447,23 @@
<li class="md-nav__item">
<a href="../selection_sort/" class="md-nav__link">
11.2. &nbsp; 选择排序
</a>
</li>
<li class="md-nav__item">
<a href="../bubble_sort/" class="md-nav__link">
11.2. &nbsp; 冒泡排序
11.3. &nbsp; 冒泡排序
</a>
</li>
@ -1461,7 +1477,7 @@
<li class="md-nav__item">
<a href="../insertion_sort/" class="md-nav__link">
11.3. &nbsp; 插入排序
11.4. &nbsp; 插入排序
</a>
</li>
@ -1475,7 +1491,7 @@
<li class="md-nav__item">
<a href="../quick_sort/" class="md-nav__link">
11.4. &nbsp; 快速排序
11.5. &nbsp; 快速排序
</a>
</li>
@ -1489,7 +1505,7 @@
<li class="md-nav__item">
<a href="../merge_sort/" class="md-nav__link">
11.5. &nbsp; 归并排序
11.6. &nbsp; 归并排序
</a>
</li>
@ -1512,12 +1528,12 @@
<label class="md-nav__link md-nav__link--active" for="__toc">
11.6. &nbsp; 桶排序
11.7. &nbsp; 桶排序
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
11.6. &nbsp; 桶排序
11.7. &nbsp; 桶排序
</a>
@ -1536,22 +1552,22 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1161" class="md-nav__link">
11.6.1. &nbsp; 算法流程
<a href="#1171" class="md-nav__link">
11.7.1. &nbsp; 算法流程
</a>
</li>
<li class="md-nav__item">
<a href="#1162" class="md-nav__link">
11.6.2. &nbsp; 算法特性
<a href="#1172" class="md-nav__link">
11.7.2. &nbsp; 算法特性
</a>
</li>
<li class="md-nav__item">
<a href="#1163" class="md-nav__link">
11.6.3. &nbsp; 如何实现平均分配
<a href="#1173" class="md-nav__link">
11.7.3. &nbsp; 如何实现平均分配
</a>
</li>
@ -1572,7 +1588,7 @@
<li class="md-nav__item">
<a href="../counting_sort/" class="md-nav__link">
11.7. &nbsp; 计数排序
11.8. &nbsp; 计数排序
</a>
</li>
@ -1586,7 +1602,7 @@
<li class="md-nav__item">
<a href="../radix_sort/" class="md-nav__link">
11.8. &nbsp; 基数排序
11.9. &nbsp; 基数排序
</a>
</li>
@ -1600,7 +1616,7 @@
<li class="md-nav__item">
<a href="../summary/" class="md-nav__link">
11.9. &nbsp; 小结
11.10. &nbsp; 小结
</a>
</li>
@ -1845,22 +1861,22 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1161" class="md-nav__link">
11.6.1. &nbsp; 算法流程
<a href="#1171" class="md-nav__link">
11.7.1. &nbsp; 算法流程
</a>
</li>
<li class="md-nav__item">
<a href="#1162" class="md-nav__link">
11.6.2. &nbsp; 算法特性
<a href="#1172" class="md-nav__link">
11.7.2. &nbsp; 算法特性
</a>
</li>
<li class="md-nav__item">
<a href="#1163" class="md-nav__link">
11.6.3. &nbsp; 如何实现平均分配
<a href="#1173" class="md-nav__link">
11.7.3. &nbsp; 如何实现平均分配
</a>
</li>
@ -1888,10 +1904,10 @@
<h1 id="116">11.6. &nbsp; 桶排序<a class="headerlink" href="#116" title="Permanent link">&para;</a></h1>
<h1 id="117">11.7. &nbsp; 桶排序<a class="headerlink" href="#117" title="Permanent link">&para;</a></h1>
<p>前述的几种排序算法都属于“基于比较的排序算法”,它们通过比较元素间的大小来实现排序。此类排序算法的时间复杂度无法超越 <span class="arithmatex">\(O(n \log n)\)</span> 。接下来,我们将探讨几种“非比较排序算法”,它们的时间复杂度可以达到线性水平。</p>
<p>「桶排序 Bucket Sort」是分治思想的一个典型应用。它通过设置一些具有大小顺序的桶每个桶对应一个数据范围将数据平均分配到各个桶中然后在每个桶内部分别执行排序最终按照桶的顺序将所有数据合并。</p>
<h2 id="1161">11.6.1. &nbsp; 算法流程<a class="headerlink" href="#1161" title="Permanent link">&para;</a></h2>
<h2 id="1171">11.7.1. &nbsp; 算法流程<a class="headerlink" href="#1171" title="Permanent link">&para;</a></h2>
<p>考虑一个长度为 <span class="arithmatex">\(n\)</span> 的数组,元素是范围 <span class="arithmatex">\([0, 1)\)</span> 的浮点数。桶排序的流程如下:</p>
<ol>
<li>初始化 <span class="arithmatex">\(k\)</span> 个桶,将 <span class="arithmatex">\(n\)</span> 个元素分配到 <span class="arithmatex">\(k\)</span> 个桶中;</li>
@ -2154,12 +2170,14 @@
<p class="admonition-title">桶排序的适用场景是什么?</p>
<p>桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。</p>
</div>
<h2 id="1162">11.6.2. &nbsp; 算法特性<a class="headerlink" href="#1162" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n + k)\)</span></strong> :假设元素在各个桶内平均分布,那么每个桶内的元素数量为 <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> 。合并结果时需要遍历 <span class="arithmatex">\(n\)</span> 个桶,花费 <span class="arithmatex">\(O(k)\)</span> 时间。</p>
<p>在最坏情况下,所有数据被分配到一个桶中,且排序该桶使用 <span class="arithmatex">\(O(n^2)\)</span> 时间,因此是“自适应排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n + k)\)</span></strong> :需要借助 <span class="arithmatex">\(k\)</span> 个桶和总共 <span class="arithmatex">\(n\)</span> 个元素的额外空间,属于“非原地排序”</p>
<p>桶排序是否稳定取决于排序桶内元素的算法是否稳定</p>
<h2 id="1163">11.6.3. &nbsp; 如何实现平均分配<a class="headerlink" href="#1163" title="Permanent link">&para;</a></h2>
<h2 id="1172">11.7.2. &nbsp; 算法特性<a class="headerlink" href="#1172" title="Permanent link">&para;</a></h2>
<ul>
<li><strong>时间复杂度 <span class="arithmatex">\(O(n + k)\)</span></strong> :假设元素在各个桶内平均分布,那么每个桶内的元素数量为 <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> 。合并结果时需要遍历 <span class="arithmatex">\(n\)</span> 个桶,花费 <span class="arithmatex">\(O(k)\)</span> 时间。</li>
<li><strong>自适应排序</strong>:在最坏情况下,所有数据被分配到一个桶中,且排序该桶使用 <span class="arithmatex">\(O(n^2)\)</span> 时间</li>
<li><strong>空间复杂度 <span class="arithmatex">\(O(n + k)\)</span> 、非原地排序</strong> :需要借助 <span class="arithmatex">\(k\)</span> 个桶和总共 <span class="arithmatex">\(n\)</span> 个元素的额外空间</li>
<li>桶排序是否稳定取决于排序桶内元素的算法是否稳定。</li>
</ul>
<h2 id="1173">11.7.3. &nbsp; 如何实现平均分配<a class="headerlink" href="#1173" title="Permanent link">&para;</a></h2>
<p>桶排序的时间复杂度理论上可以达到 <span class="arithmatex">\(O(n)\)</span> <strong>关键在于将元素均匀分配到各个桶中</strong>,因为实际数据往往不是均匀分布的。例如,我们想要将淘宝上的所有商品按价格范围平均分配到 10 个桶中,但商品价格分布不均,低于 100 元的非常多,高于 1000 元的非常少。若将价格区间平均划分为 10 份,各个桶中的商品数量差距会非常大。</p>
<p>为实现平均分配,我们可以先设定一个大致的分界线,将数据粗略地分到 3 个桶中。<strong>分配完毕后,再将商品较多的桶继续划分为 3 个桶,直至所有桶中的元素数量大致相等</strong>。这种方法本质上是创建一个递归树,使叶节点的值尽可能平均。当然,不一定要每轮将数据划分为 3 个桶,具体划分方式可根据数据特点灵活选择。</p>
<p><img alt="递归划分桶" src="../bucket_sort.assets/scatter_in_buckets_recursively.png" /></p>
@ -2245,7 +2263,7 @@
<nav class="md-footer__inner md-grid" aria-label="页脚" >
<a href="../merge_sort/" class="md-footer__link md-footer__link--prev" aria-label="上一页: 11.5. &amp;nbsp; 归并排序" rel="prev">
<a href="../merge_sort/" class="md-footer__link md-footer__link--prev" aria-label="上一页: 11.6. &amp;nbsp; 归并排序" rel="prev">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</div>
@ -2254,20 +2272,20 @@
上一页
</span>
<div class="md-ellipsis">
11.5. &nbsp; 归并排序
11.6. &nbsp; 归并排序
</div>
</div>
</a>
<a href="../counting_sort/" class="md-footer__link md-footer__link--next" aria-label="下一页: 11.7. &amp;nbsp; 计数排序" rel="next">
<a href="../counting_sort/" class="md-footer__link md-footer__link--next" aria-label="下一页: 11.8. &amp;nbsp; 计数排序" rel="next">
<div class="md-footer__title">
<span class="md-footer__direction">
下一页
</span>
<div class="md-ellipsis">
11.7. &nbsp; 计数排序
11.8. &nbsp; 计数排序
</div>
</div>
<div class="md-footer__button md-icon">