This commit is contained in:
krahets
2023-07-26 08:59:03 +08:00
parent fd34c845bc
commit 974fea7de4
48 changed files with 299 additions and 299 deletions
+3 -3
View File
@@ -3393,9 +3393,9 @@
<h2 id="1181">11.8.1. &nbsp; 算法流程<a class="headerlink" href="#1181" 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>
<li>对每个桶分别执行排序(本文采用编程语言的内置排序函数)</li>
<li>按照桶的从小到大的顺序,合并结果</li>
<li>初始化 <span class="arithmatex">\(k\)</span> 个桶,将 <span class="arithmatex">\(n\)</span> 个元素分配到 <span class="arithmatex">\(k\)</span> 个桶中</li>
<li>对每个桶分别执行排序(本文采用编程语言的内置排序函数)</li>
<li>按照桶的从小到大的顺序,合并结果</li>
</ol>
<p><img alt="桶排序算法流程" src="../bucket_sort.assets/bucket_sort_overview.png" /></p>
<p align="center"> Fig. 桶排序算法流程 </p>
+3 -3
View File
@@ -3406,7 +3406,7 @@
<h2 id="1191">11.9.1. &nbsp; 简单实现<a class="headerlink" href="#1191" title="Permanent link">&para;</a></h2>
<p>先来看一个简单的例子。给定一个长度为 <span class="arithmatex">\(n\)</span> 的数组 <code>nums</code> ,其中的元素都是“非负整数”。计数排序的整体流程如下:</p>
<ol>
<li>遍历数组,找出数组中的最大数字,记为 <span class="arithmatex">\(m\)</span> ,然后创建一个长度为 <span class="arithmatex">\(m + 1\)</span> 的辅助数组 <code>counter</code> </li>
<li>遍历数组,找出数组中的最大数字,记为 <span class="arithmatex">\(m\)</span> ,然后创建一个长度为 <span class="arithmatex">\(m + 1\)</span> 的辅助数组 <code>counter</code> </li>
<li><strong>借助 <code>counter</code> 统计 <code>nums</code> 中各数字的出现次数</strong>,其中 <code>counter[num]</code> 对应数字 <code>num</code> 的出现次数。统计方法很简单,只需遍历 <code>nums</code>(设当前数字为 <code>num</code>),每轮将 <code>counter[num]</code> 增加 <span class="arithmatex">\(1\)</span> 即可。</li>
<li><strong>由于 <code>counter</code> 的各个索引天然有序,因此相当于所有数字已经被排序好了</strong>。接下来,我们遍历 <code>counter</code> ,根据各数字的出现次数,将它们按从小到大的顺序填入 <code>nums</code> 即可。</li>
</ol>
@@ -3681,8 +3681,8 @@
\]</div>
<p><strong>前缀和具有明确的意义,<code>prefix[num] - 1</code> 代表元素 <code>num</code> 在结果数组 <code>res</code> 中最后一次出现的索引</strong>。这个信息非常关键,因为它告诉我们各个元素应该出现在结果数组的哪个位置。接下来,我们倒序遍历原数组 <code>nums</code> 的每个元素 <code>num</code> ,在每轮迭代中执行:</p>
<ol>
<li><code>num</code> 填入数组 <code>res</code> 的索引 <code>prefix[num] - 1</code></li>
<li>令前缀和 <code>prefix[num]</code> 减小 <span class="arithmatex">\(1\)</span> ,从而得到下次放置 <code>num</code> 的索引</li>
<li><code>num</code> 填入数组 <code>res</code> 的索引 <code>prefix[num] - 1</code></li>
<li>令前缀和 <code>prefix[num]</code> 减小 <span class="arithmatex">\(1\)</span> ,从而得到下次放置 <code>num</code> 的索引</li>
</ol>
<p>遍历完成后,数组 <code>res</code> 中就是排序好的结果,最后使用 <code>res</code> 覆盖原数组 <code>nums</code> 即可。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:8"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">&lt;1&gt;</label><label for="__tabbed_2_2">&lt;2&gt;</label><label for="__tabbed_2_3">&lt;3&gt;</label><label for="__tabbed_2_4">&lt;4&gt;</label><label for="__tabbed_2_5">&lt;5&gt;</label><label for="__tabbed_2_6">&lt;6&gt;</label><label for="__tabbed_2_7">&lt;7&gt;</label><label for="__tabbed_2_8">&lt;8&gt;</label></div>
+6 -6
View File
@@ -3390,17 +3390,17 @@
<h1 id="116">11.6. &nbsp; 归并排序<a class="headerlink" href="#116" title="Permanent link">&para;</a></h1>
<p>「归并排序 Merge Sort」基于分治思想实现排序,包含“划分”和“合并”两个阶段:</p>
<ol>
<li><strong>划分阶段</strong>:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题</li>
<li><strong>合并阶段</strong>:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束</li>
<li><strong>划分阶段</strong>:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题</li>
<li><strong>合并阶段</strong>:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束</li>
</ol>
<p><img alt="归并排序的划分与合并阶段" src="../merge_sort.assets/merge_sort_overview.png" /></p>
<p align="center"> Fig. 归并排序的划分与合并阶段 </p>
<h2 id="1161">11.6.1. &nbsp; 算法流程<a class="headerlink" href="#1161" title="Permanent link">&para;</a></h2>
<p>“划分阶段”从顶至底递归地将数组从中点切为两个子数组,直至长度为 1 </p>
<p>“划分阶段”从顶至底递归地将数组从中点切为两个子数组</p>
<ol>
<li>计算数组中点 <code>mid</code> ,递归划分左子数组(区间 <code>[left, mid]</code> )和右子数组(区间 <code>[mid + 1, right]</code> </li>
<li>递归执行步骤 <code>1.</code> ,直至子数组区间长度为 1 时,终止递归划分</li>
<li>计算数组中点 <code>mid</code> ,递归划分左子数组(区间 <code>[left, mid]</code> )和右子数组(区间 <code>[mid + 1, right]</code> </li>
<li>递归执行步骤 <code>1.</code> ,直至子数组区间长度为 1 时,终止递归划分</li>
</ol>
<p>“合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:10"><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" /><div class="tabbed-labels"><label for="__tabbed_1_1">&lt;1&gt;</label><label for="__tabbed_1_2">&lt;2&gt;</label><label for="__tabbed_1_3">&lt;3&gt;</label><label for="__tabbed_1_4">&lt;4&gt;</label><label for="__tabbed_1_5">&lt;5&gt;</label><label for="__tabbed_1_6">&lt;6&gt;</label><label for="__tabbed_1_7">&lt;7&gt;</label><label for="__tabbed_1_8">&lt;8&gt;</label><label for="__tabbed_1_9">&lt;9&gt;</label><label for="__tabbed_1_10">&lt;10&gt;</label></div>
@@ -3952,7 +3952,7 @@
<p>归并排序在排序链表时具有显著优势,空间复杂度可以优化至 <span class="arithmatex">\(O(1)\)</span> ,原因如下:</p>
<ul>
<li>由于链表仅需改变指针就可实现节点的增删操作,因此合并阶段(将两个短有序链表合并为一个长有序链表)无需创建辅助链表。</li>
<li>通过使用“迭代划分”替代“递归划分”,可省去递归使用的栈帧空间</li>
<li>通过使用“迭代划分”替代“递归划分”,可省去递归使用的栈帧空间</li>
</ul>
<p>具体实现细节比较复杂,有兴趣的同学可以查阅相关资料进行学习。</p>
+6 -6
View File
@@ -3419,9 +3419,9 @@
<p>「快速排序 Quick Sort」是一种基于分治思想的排序算法,运行高效,应用广泛。</p>
<p>快速排序的核心操作是「哨兵划分」,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程为:</p>
<ol>
<li>选取数组最左端元素作为基准数,初始化两个指针 <code>i</code><code>j</code> 分别指向数组的两端</li>
<li>设置一个循环,在每轮中使用 <code>i</code><code>j</code>)分别寻找第一个比基准数大(小)的元素,然后交换这两个元素</li>
<li>循环执行步骤 <code>2.</code> ,直到 <code>i</code><code>j</code> 相遇时停止,最后将基准数交换至两个子数组的分界线</li>
<li>选取数组最左端元素作为基准数,初始化两个指针 <code>i</code><code>j</code> 分别指向数组的两端</li>
<li>设置一个循环,在每轮中使用 <code>i</code><code>j</code>)分别寻找第一个比基准数大(小)的元素,然后交换这两个元素</li>
<li>循环执行步骤 <code>2.</code> ,直到 <code>i</code><code>j</code> 相遇时停止,最后将基准数交换至两个子数组的分界线</li>
</ol>
<p>哨兵划分完成后,原数组被划分成三部分:左子数组、基准数、右子数组,且满足“左子数组任意元素 <span class="arithmatex">\(\leq\)</span> 基准数 <span class="arithmatex">\(\leq\)</span> 右子数组任意元素”。因此,我们接下来只需对这两个子数组进行排序。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:9"><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" /><div class="tabbed-labels"><label for="__tabbed_1_1">&lt;1&gt;</label><label for="__tabbed_1_2">&lt;2&gt;</label><label for="__tabbed_1_3">&lt;3&gt;</label><label for="__tabbed_1_4">&lt;4&gt;</label><label for="__tabbed_1_5">&lt;5&gt;</label><label for="__tabbed_1_6">&lt;6&gt;</label><label for="__tabbed_1_7">&lt;7&gt;</label><label for="__tabbed_1_8">&lt;8&gt;</label><label for="__tabbed_1_9">&lt;9&gt;</label></div>
@@ -3735,9 +3735,9 @@
</div>
<h2 id="1151">11.5.1. &nbsp; 算法流程<a class="headerlink" href="#1151" title="Permanent link">&para;</a></h2>
<ol>
<li>首先,对原数组执行一次「哨兵划分」,得到未排序的左子数组和右子数组</li>
<li>然后,对左子数组和右子数组分别递归执行「哨兵划分」</li>
<li>持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序</li>
<li>首先,对原数组执行一次「哨兵划分」,得到未排序的左子数组和右子数组</li>
<li>然后,对左子数组和右子数组分别递归执行「哨兵划分」</li>
<li>持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序</li>
</ol>
<p><img alt="快速排序流程" src="../quick_sort.assets/quick_sort_overview.png" /></p>
<p align="center"> Fig. 快速排序流程 </p>
+3 -3
View File
@@ -3379,9 +3379,9 @@
<h2 id="11101">11.10.1. &nbsp; 算法流程<a class="headerlink" href="#11101" title="Permanent link">&para;</a></h2>
<p>以学号数据为例,假设数字的最低位是第 <span class="arithmatex">\(1\)</span> 位,最高位是第 <span class="arithmatex">\(8\)</span> 位,基数排序的步骤如下:</p>
<ol>
<li>初始化位数 <span class="arithmatex">\(k = 1\)</span> </li>
<li>对学号的第 <span class="arithmatex">\(k\)</span> 位执行「计数排序」。完成后,数据会根据第 <span class="arithmatex">\(k\)</span> 位从小到大排序</li>
<li><span class="arithmatex">\(k\)</span> 增加 <span class="arithmatex">\(1\)</span> ,然后返回步骤 <code>2.</code> 继续迭代,直到所有位都排序完成后结束</li>
<li>初始化位数 <span class="arithmatex">\(k = 1\)</span> </li>
<li>对学号的第 <span class="arithmatex">\(k\)</span> 位执行「计数排序」。完成后,数据会根据第 <span class="arithmatex">\(k\)</span> 位从小到大排序</li>
<li><span class="arithmatex">\(k\)</span> 增加 <span class="arithmatex">\(1\)</span> ,然后返回步骤 <code>2.</code> 继续迭代,直到所有位都排序完成后结束</li>
</ol>
<p><img alt="基数排序算法流程" src="../radix_sort.assets/radix_sort_overview.png" /></p>
<p align="center"> Fig. 基数排序算法流程 </p>