毕妗滟 發表於 2026-3-26 16:08:00

AI时代的算法思维:10大经典排序学习

<h1 id="ai时代重温10大经典排序算法">AI时代,重温10大经典排序算法</h1>
<p>AI可以轻松生成任何排序算法代码,那么我们还有必要学习算法吗?</p>
<blockquote>
<p>有必要。我们需要理解算法背后的思想——分治、贪心、空间换时间以及分桶映射等。掌握这些思想,才能更好地跟AI协作。</p>
</blockquote>
<h2 id="一为什么还要学排序算法">一、为什么还要学排序算法?</h2>
<h3 id="排序无处不在">排序无处不在</h3>
<p>信息流、搜索结果、商品列表、好友排名,背后都有排序算法在工作。从数据库的 ORDER BY,到搜索引擎排序、推荐系统的优先级队列,本质上都是排序问题。</p>
<p>学习算法,本质是学习解决问题的思路,帮助我们在实际场景中做出更合理的决策。</p>
<ul>
<li>100万条订单数据,应该用快速排序还是归并排序?为什么?</li>
<li>用户ID是纯数字且范围有限,能不能用计数排序把O(n log n)优化到O(n)?</li>
<li>排序结果传递给下游做二次排序好,还是提前排好?性能和稳定性如何权衡?</li>
</ul>
<h3 id="排序算法是算法思想的缩影">排序算法是算法思想的缩影</h3>
<p>十大排序算法并不只是十个简单的程序,它们背后浓缩了计算机大师们的心血,是几种核心算法思想的具体体现:</p>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 30, 'padding': 20}}}%%
graph TD
    ROOT(["排序算法的核心思想"]):::root

    ROOT --&gt; A["分治思想"]
    ROOT --&gt; B["贪心思想"]
    ROOT --&gt; C["插入思想"]
    ROOT --&gt; D["交换思想"]
    ROOT --&gt; E["映射思想"]
    ROOT --&gt; F["树形结构"]

    A --&gt; A1["快速排序\n归并排序"]
    B --&gt; B1["选择排序"]
    C --&gt; C1["插入排序\n希尔排序"]
    D --&gt; D1["冒泡排序"]
    E --&gt; E1["计数排序\n基数排序\n桶排序"]
    F --&gt; F1["堆排序"]

    %% root更突出
    classDef root fill:#111827,color:#ffffff,stroke:#000000,stroke-width:2px,rx:12,ry:12

    %% 分类层
    style A fill:#11908A,stroke:#0F6E56,color:#ffffff,rx:10,ry:10
    style B fill:#534AB7,stroke:#3C3489,color:#ffffff,rx:10,ry:10
    style C fill:#D85A30,stroke:#993C1D,color:#ffffff,rx:10,ry:10
    style D fill:#BA7517,stroke:#854F0B,color:#ffffff,rx:10,ry:10
    style E fill:#185FA5,stroke:#0C447C,color:#ffffff,rx:10,ry:10
    style F fill:#993556,stroke:#72243E,color:#ffffff,rx:10,ry:10

    %% 叶子层
    style A1 fill:#11908A,stroke:#0F6E56,color:#ffffff,rx:10,ry:10
    style B1 fill:#534AB7,stroke:#3C3489,color:#ffffff,rx:10,ry:10
    style C1 fill:#D85A30,stroke:#993C1D,color:#ffffff,rx:10,ry:10
    style D1 fill:#BA7517,stroke:#854F0B,color:#ffffff,rx:10,ry:10
    style E1 fill:#185FA5,stroke:#0C447C,color:#ffffff,rx:10,ry:10
    style F1 fill:#993556,stroke:#72243E,color:#ffffff,rx:10,ry:10
</div><p>排序算法是学习算法思想的切入点,通过它,我们可以学习到<strong>分解问题、选择策略、优化性能</strong>的思维方式。</p>
<hr>
<h2 id="二排序算法全景图">二、排序算法全景图</h2>
<h3 id="排序算法分类体系">排序算法分类体系</h3>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 15, 'rankSpacing': 25, 'padding': 20}}}%%
graph TD
    ROOT(["十大排序算法"])
    ROOT --&gt; CMP("比较排序\n理论下界 O(n log n)")
    ROOT --&gt; NCMP("非比较排序\n可突破 O(n log n)")

    CMP --&gt; SWAP["交换排序"]
    CMP --&gt; SEL["选择排序"]
    CMP --&gt; INS["插入排序"]
    CMP --&gt; MRG["归并排序"]

    SWAP --&gt; BUB["冒泡排序"]
    SWAP --&gt; QCK["快速排序"]
    SEL --&gt; SSEL["选择排序"]
    SEL --&gt; HEAP["堆排序"]
    INS --&gt; DINS["插入排序"]
    INS --&gt; SHELL["希尔排序"]

    NCMP --&gt; CNT["计数排序"]
    NCMP --&gt; RDX["基数排序"]
    NCMP --&gt; BKT["桶排序"]

    classDef root fill:#111827,stroke:#2C2C2A,color:#fff
    classDef cat fill:#11908A,color:#fff,stroke:#0a2647
    classDef sub fill:#533483,color:#fff,stroke:#2c1654
    classDef leaf fill:#e94560,color:#fff,stroke:#c81e45

    class ROOT root
    class CMP,NCMP cat
    class SWAP,SEL,INS,MRG sub
    class BUB,QCK,SSEL,HEAP,DINS,SHELL,CNT,RDX,BKT leaf
</div><p><strong>排序可以分为两大类别:</strong></p>
<p><strong>1. 比较排序</strong>:通过元素之间两两比较来排序。其时间复杂度存在下界 O(n log n),无论采用多高效的比较策略,都难以突破这一限制。</p>
<p><strong>2. 非比较排序</strong>:利用数据本身的特征(如数值范围、位数等)直接确定元素位置,在特定条件下可达到线性时间 O(n)。但它对数据有一定要求,例如取值范围有限、可按位分解,或能够映射到固定区间。</p>
<hr>
<h2 id="三十大排序算法详解">三、十大排序算法详解</h2>
<h3 id="1-冒泡排序bubble-sort-最朴素的交换">1. 冒泡排序(Bubble Sort)— 最朴素的交换</h3>
<p><strong>算法原理</strong>:遍历数组,相邻元素两两比较,将较大的项交换到右侧。每一轮遍历都会把当前最大的元素"冒泡"到数组末尾,就像汽水里的气泡一样往上浮。如果某一轮没有发生交换,说明数组已经有序,可以提前结束。</p>
<p>它和选择排序的思路相反:选择排序是“每次选出一个最小(或最大)放到末尾”,而冒泡排序是“通过不断交换,让最大(或最小)逐步移动到末尾”。</p>
<blockquote>
<p><strong>生活类比</strong>:体育课排队,让相邻两人比身高,矮的站前面,高的站后面。一轮下来,最高的人一定到了队尾。</p>
</blockquote>
<h4 id="流程图">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 25, 'padding': 15}}}%%
graph LR

    S(["开始"]) --&gt; INIT["未排序区间 &lt;br&gt; = "]

    INIT --&gt; OUTER{"区间长度 &gt; 1 ?"}
    OUTER --&gt;|"否"| END(["排序完成"])

    OUTER --&gt;|"是"| LOOP["从左到右&lt;br&gt;依次比较相邻元素"]

    LOOP --&gt; CMP{"前一个 &gt; 后一个 ?"}

    CMP --&gt;|"否"| NEXT["继续比较"]
    CMP --&gt;|"是"| SWAP["交换"]

    SWAP --&gt; NEXT

    NEXT --&gt; CHECK{"到达区间末尾 ?"}

    CHECK --&gt;|"否"| LOOP

    CHECK --&gt;|"是"| SHRINK["缩小区间&lt;br&gt;(最大值归位)"]

    SHRINK --&gt; |"开始下一轮"| OUTER

    %% 样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    class S,END start
    class OUTER,CMP,CHECK decision
    class INIT,LOOP,NEXT,SWAP,SHRINK process
</div><h4 id="伪代码">伪代码</h4>
<pre><code class="language-js">function BubbleSort(arr):
    n = length(arr)
    for i = 0 to n - 2:                     // 外层循环:共 n-1 轮
      swapped = false                     // 优化点:设置已交换标志
      for j = 0 to n - 2 - i:             // 内层循环:每轮减少一个
            if arr &gt; arr:         // 相邻比较
                swap(arr, arr)    // 逆序则交换
                swapped = true
      if not swapped:                     // 本轮无交换,已有序,跳过
            break
    return arr
</code></pre>
<h4 id="应用场景">应用场景</h4>
<ul>
<li><strong>算法入门教学</strong>:逻辑最简单、最直观,是所有算法教材的首选入门排序</li>
<li><strong>近乎有序的小规模数据</strong>:加入提前终止优化后,对基本有序的数据可达到 O (n) 效率</li>
<li><strong>嵌入式与极简环境</strong>:代码极短、占用空间极小,适合资源受限设备</li>
</ul>
<h4 id="复杂度">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n)</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>冒泡排序非常好理解,性能也很稳定,虽然现实中使用不多,但是因为很简单、很形象,所以通常是学习排序算法的第一课。</p>
</blockquote>
<hr>
<h3 id="2-选择排序selection-sort-最少的交换">2. 选择排序(Selection Sort)— 最少的交换</h3>
<p><strong>算法原理</strong>:遍历数组,每一轮在未排序区域中选出最小(或最大)的元素,放到已排序区域的起始位置(或末尾)。整体需要进行大量比较,但交换次数很少,每一轮最多只交换一次。</p>
<p>它和插入排序的思路正好相反:选择排序是“先选一个,再放过去”;而插入排序是“从未排序中取一个,按顺序插入到已排序里”。</p>
<blockquote>
<p><strong>生活类比</strong>:就像从一堆没有次序的苹果里,每次都挑出最小(或最大)的一个,放到一边按大小排好。每次只挑一个,慢慢就把所有苹果按顺序排好了。</p>
</blockquote>
<h4 id="流程图-1">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 25, 'padding': 15}}}%%
graph LR

    S(["开始"]) --&gt; INIT["未排序区间 = "]

    INIT --&gt; OUTER{"区间长度 &gt; 1 ?"}
    OUTER --&gt;|"否"| END(["排序完成"])

    OUTER --&gt;|"是"| FIND["在未排序区间&lt;br&gt;挑选最小值"]

    FIND --&gt; DOSWAP{"最小值是否&lt;br&gt;在当前位置 ?"}
    DOSWAP --&gt;|"否"| SWAP["交换最小值&lt;br&gt;到当前位置"]
    DOSWAP --&gt;|"是"| NEXT["无需交换"]

    SWAP --&gt; NEXT

    NEXT --&gt; SHRINK["缩小未排序区间&lt;br&gt;长度 - 1"]
    SHRINK --&gt; |"开始下一轮"| OUTER

    %% 样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    class S,END start
    class OUTER,DOSWAP decision
    class INIT,FIND,SWAP,NEXT,SHRINK process
</div><h4 id="伪代码-1">伪代码</h4>
<pre><code class="language-js">function SelectionSort(arr):
    n = length(arr)
    for i = 0 to n - 2:                     // 遍历每个位置
      minIdx = i                        // 假设当前位置是最小值
      for j = i + 1 to n - 1:             // 在未排序区间找最小值
            if arr &lt; arr:
                minIdx = j                  // 更新最小值位置
      if minIdx != i:
            swap(arr, arr)       // 只交换一次
    return arr
</code></pre>
<h4 id="应用场景-1">应用场景</h4>
<ul>
<li><strong>写入敏感的存储设备</strong>:如 Flash、ROM 等,选择排序交换次数最少,能显著降低写入损耗</li>
<li><strong>小规模数据排序</strong>:实现简单、逻辑清晰,数据量小时性能差异不明显</li>
<li><strong>朴素 Top‑K 问题</strong>:只需执行 K 轮即可选出前 K 大 / 前 K 小元素,无需完整排序</li>
</ul>
<h4 id="复杂度-1">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>不稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>选择排序不太稳定,因为交换时可能把原本顺序相同的元素颠倒掉。比如 <code></code>,第一轮把4和第一个5交换,第一个5就跑到最后面了。选择排序也是非常好理解的方式,每次从剩下的一堆里把最大或最小的挑出来,放到已排序里面去。</p>
</blockquote>
<hr>
<h3 id="3-插入排序insertion-sort-扑克牌式排序">3. 插入排序(Insertion Sort)— 扑克牌式排序</h3>
<p><strong>算法原理</strong>:遍历数组,把数组分为已排序和未排序两部分,每次从未排序区间取出一个元素,插入到已排序区间的合适位置,已排序中的较大元素会依次向后移动。</p>
<blockquote>
<p>生活类比:就像打扑克牌一样,把牌分成两堆——<strong>已排好序的牌</strong>和<strong>未排的牌</strong>。一开始,第一张牌算作已排序部分,其余都是未排序部分。然后每次从未排序中拿一张牌,插入到已排序中的正确位置,已排序的牌往右移一位,未排序的牌则减少一张。</p>
</blockquote>
<h4 id="流程图-2">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 25, 'padding': 15}}}%%
graph LR

    S(["开始"]) --&gt; INIT["未排序区间 &lt;br&gt;= "]

    INIT --&gt; OUTER{"区间长度 &gt; 0 ?"}
    OUTER --&gt;|"否"| END(["排序完成"])
    OUTER --&gt;|"是"| KEY["选取当前元素&lt;br&gt;作为待插入 key"]

    KEY --&gt; INNER{"前方元素&lt;br&gt;是否 &gt; key ?"}
    INNER --&gt;|"是"| SHIFT["向后移动元素&lt;br&gt;为 key 腾位置"]
    SHIFT --&gt; INNER
    INNER --&gt;|"否"| PLACE["将 key 放入合适位置"]

    PLACE --&gt; SHRINK["未排序区间长度 - 1"]
    SHRINK --&gt; |"开始下一轮"| OUTER

    %% 样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    class S,END start
    class OUTER,INNER decision
    class INIT,KEY,SHIFT,PLACE,SHRINK process
</div><h4 id="伪代码-2">伪代码</h4>
<pre><code class="language-js">function InsertionSort(arr):
    n = length(arr)
    for i = 1 to n - 1:                  // 从第2个元素开始
      key = arr                     // 取出待插入的牌
      j = i - 1
      while j &gt;= 0 and arr &gt; key:   // 从右往左找位置
            arr = arr            // 比key大的元素后移
            j = j - 1
      arr = key                   // 插入到正确位置
    return arr
</code></pre>
<h4 id="应用场景-2">应用场景</h4>
<ul>
<li><strong>小规模数据排序</strong>:n &lt; 50时,插入排序的常数因子极小,实际速度往往最快</li>
<li><strong>Timsort的子过程</strong>:Python的<code>sorted()</code>和Java的<code>Collections.sort()</code>底层都用插入排序处理小分区</li>
<li><strong>在线流式排序</strong>:数据逐个到达时,可随时插入到正确位置,无需重排整体</li>
<li><strong>近乎有序的数据</strong>:最优情况可达到 O (n),是简单排序中效率最高的一种</li>
</ul>
<h4 id="复杂度-2">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n)</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>插入排序稳定、直观,对小规模或几乎有序的数据特别高效,就像整理扑克牌一样,这是我们大家都会的方式,是经过实践检验的经典算法。</p>
</blockquote>
<hr>
<h3 id="4-希尔排序shell-sort-跳跃式插入">4. 希尔排序(Shell Sort)— 跳跃式插入</h3>
<p><strong>算法原理</strong>:希尔排序是插入排序的改进版。它先使用较大的步长(gap)将数组划分为若干组,对每组分别进行插入排序,然后逐步缩小 gap,最终在 gap = 1 时完成整体排序。通过先对间隔较大的子序列进行排序,可以显著减少元素的移动次数,从而提高整体效率,尤其适用于规模较大或初始无序程度较高的数据。</p>
<blockquote>
<p>生活类比:就像整理扑克牌,如果手里有很多牌,一次只按相隔一定间距(比如每隔10张牌)把牌插入到已排好的位置,先把大块牌大致排好序,再缩小间距,一次次精细调整,最后整个牌堆就排好了。相比插入排序“每次拿一张牌插入”,希尔排序就像先粗略排,再精细排,效率更高。</p>
</blockquote>
<h4 id="流程图-3">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 25, 'padding': 15}}}%%
graph LR

    S(["开始"]) --&gt; GAP["初始化&lt;br&gt;gap = n / 2"]

    GAP --&gt; GCHK{"gap &gt; 0 ?"}
    GCHK --&gt;|"否"| END(["排序完成"])
    GCHK --&gt;|"是"| OUTER["遍历未排序区间&lt;br&gt;i = gap → n-1"]

    OUTER --&gt; KEY["取当前元素&lt;br&gt;作为待插入 key"]

    KEY --&gt; INNER{"按 gap 向前比较&lt;br&gt;前方元素是否 &gt; key ?"}
    INNER --&gt;|"是"| SHIFT["向后移动元素&lt;br&gt;为 key 腾位置"]
    SHIFT --&gt; INNER
    INNER --&gt;|"否"| PLACE["将 key 插入&lt;br&gt;正确位置"]

    PLACE --&gt; NEXT["处理下一个 i"]
    NEXT --&gt; OUTER

    OUTER --&gt; GSHR["gap /= 2"]
    GSHR --&gt; |"开始下一轮"| GCHK

    %% 样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    class S,END start
    class GCHK,INNER decision
    class GAP,OUTER,KEY,SHIFT,PLACE,NEXT,GSHR process
</div><h4 id="伪代码-3">伪代码</h4>
<pre><code class="language-js">function ShellSort(arr):
    n = length(arr)
    gap = n / 2                           // 初始步长
    while gap &gt; 0:                        // 逐步缩小步长
      for i = gap to n - 1:               // 对每个分组做插入排序
            key = arr
            j = i - gap
            while j &gt;= 0 and arr &gt; key:// 组内插入排序
                arr = arr
                j = j - gap
            arr = key
      gap = gap / 2                     // 步长减半
    return arr
</code></pre>
<h4 id="应用场景-3">应用场景</h4>
<ul>
<li><strong>中等规模数据(100~10000 条)</strong>:效率远超 O (n²) 算法,又不需要快排的递归栈空间</li>
<li><strong>嵌入式与小型系统</strong>:原地排序、代码简洁,性能稳定</li>
<li><strong>Linux 内核等底层场景</strong>:部分版本使用希尔排序处理中等规模数据排序</li>
</ul>
<h4 id="复杂度-3">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n log n)</td>
<td>O(n^1.3)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>不稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>希尔排序的时间复杂度取决于步长(gap)的选择。希尔建议的初始步长是 N/2,即每次把数组分成两半进行排序。这种取法在大多数情况下比直接插入排序效率高,但也并不是最优选择。希尔排序给我们的启迪是将大量数据拆分成小组来处理。</p>
</blockquote>
<hr>
<h3 id="5-快速排序quick-sort-分治的经典">5. 快速排序(Quick Sort)— 分治的经典</h3>
<p><strong>算法原理</strong>:选一个基准元素(pivot),把数组划分成两部分:一部分比它小,一部分比它大,然后分别对这两部分继续做同样的操作。通过不断拆分,直到每一部分都有序,整体自然就完成排序。快速排序是分治思想的体现:先把大问题拆成小问题,再逐个击破。</p>
<blockquote>
<p><strong>生活类比</strong>:就像给一群人排队,先选一个人当“基准”,比他矮的站左边,比他高的站右边,然后左右两边也重复这个过程。随着不断分组,每一小组都会越来越有序,直到每组只剩下一个人时,整个队伍就排好了。</p>
</blockquote>
<h4 id="流程图-4">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 25, 'padding': 15}}}%%
graph LR

    S(["开始"]) --&gt; CHK["当前区间长度 &gt; 1 ?"]
    CHK --&gt;|"否"| END(["返回"])
    CHK --&gt;|"是"| PIVOT["选择 pivot 元素"]

    PIVOT --&gt; PART["分区操作:&lt;br&gt;小于 pivot 在左&lt;br&gt;大于 pivot 在右&lt;br&gt;返回 pivot 位置 p"]

    PART --&gt; LEFT["递归排序左半区&lt;br&gt;quickSort(low, p-1)"]
    LEFT --&gt; RIGHT["递归排序右半区&lt;br&gt;quickSort(p+1, high)"]
    RIGHT --&gt; |"返回上一层"| CHK

    %% 节点样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef pivot fill:#ffb703,color:#000,stroke:#e09f00,stroke-width:2px
    classDef partition fill:#A3F1B5,color:#003d2e,stroke:#04a777,stroke-width:2px
    classDef recurse fill:#11908A,color:#fff,stroke:#0b5f7a,stroke-width:2px

    %% 应用样式
    class S,END start
    class CHK decision
    class PIVOT pivot
    class PART partition
    class LEFT,RIGHT recurse
</div><h4 id="伪代码-4">伪代码</h4>
<pre><code class="language-js">function QuickSort(arr, low, high):
    if low &lt; high:
      p = Partition(arr, low, high)       // 分区,返回pivot的最终位置
      QuickSort(arr, low, p - 1)          // 递归排序左半部分
      QuickSort(arr, p + 1, high)         // 递归排序右半部分

function Partition(arr, low, high):
    pivot = arr                     // 选最后一个元素作为基准
    i = low - 1                           // i 指向"小于pivot区域"的末尾
    for j = low to high - 1:
      if arr &lt;= pivot:               // 当前元素比pivot小
            i = i + 1
            swap(arr, arr)            // 放到左边区域
    swap(arr, arr)             // pivot放到中间
    return i + 1                            // 返回pivot的位置
</code></pre>
<h4 id="应用场景-4">应用场景</h4>
<ul>
<li><strong>通用排序的首选</strong>:C标准库的<code>qsort()</code>、Java的<code>Arrays.sort()</code>(基本类型)都基于快排变体</li>
<li><strong>数据库与搜索引擎</strong>:内存中的 ORDER BY、结果集排序大量使用快速排序</li>
<li><strong>大数据 Shuffle 阶段</strong>:MapReduce、Spark 等分布式框架广泛使用快排思想</li>
<li><strong>为什么实际最快</strong>:缓存友好(在连续内存上操作)、内层循环极其紧凑、原地排序节省内存</li>
<li><strong>通用内存排序首选</strong>:C、C++、Java、Go 等语言标准库的默认排序均基于快排变体</li>
</ul>
<h4 id="复杂度-4">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n log n)</td>
<td>O(n log n)</td>
<td>O(n²)</td>
<td>O(log n)</td>
<td>不稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>快速排序高效而优雅,通过不断选取基准、拆分左右区间,把复杂问题层层拆解,是开发实践中最常用的排序算法之一。看似简单的“分一分”,却蕴含着强大的力量,这也是分治思想的体现。</p>
</blockquote>
<hr>
<h3 id="6-归并排序merge-sort-稳定的分治">6. 归并排序(Merge Sort)— 稳定的分治</h3>
<p><strong>算法原理</strong>:每次将数组一分为二,递归对左右两半继续拆分,直到每个子数组只有一个元素(自然有序)。然后从最底层开始,逐层向上合并,将两个有序子数组合并为一个更大的有序数组,最终得到整体有序的结果。</p>
<blockquote>
<p><strong>生活类比</strong>:先把一筐苹果<strong>分成两篮</strong>,再每篮<strong>分成两篮</strong>……直到每篮只有一个苹果。然后从底层开始合并,每次合并按大小排序,直到所有苹果排好。</p>
</blockquote>
<h4 id="流程图-5">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 25, 'rankSpacing': 25, 'padding': 15}}}%%
graph LR

    S(["开始"]) --&gt; CHK["当前数组长度 &gt; 1 ?"]
    CHK --&gt;|"否"| END(["返回"])
    CHK --&gt;|"是"| SPLIT["从中间二分为&lt;br&gt;左半部分 + 右半部分"]

    SPLIT --&gt; LSORT["递归排序左半区&lt;br&gt;mergeSort(left)"]
    LSORT --&gt; RSORT["递归排序右半区&lt;br&gt;mergeSort(right)"]

    RSORT --&gt; MERGE["合并两个有序子数组&lt;br&gt;返回有序数组"]

    MERGE --&gt; |"返回上一层"| CHK

    %% 节点样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    %% 应用样式
    class S,END start
    class CHK decision
    class SPLIT,LSORT,RSORT,MERGE process
</div><h4 id="伪代码-5">伪代码</h4>
<pre><code class="language-js">function MergeSort(arr):
    if length(arr) &lt;= 1:
      return arr                        // 单个元素自然有序
    mid = length(arr) / 2
    left = MergeSort(arr)          // 递归排序左半部分
    right = MergeSort(arr)       // 递归排序右半部分
    return Merge(left, right)               // 合并两个有序数组

function Merge(left, right):
    result = []
    i = 0, j = 0
    while i &lt; length(left) and j &lt; length(right):
      if left &lt;= right:             // 取出小的
            result.append(left)
            i++
      else:
            result.append(right)
            j++
    result.append(left)               // 追加剩余元素
    result.append(right)
    return result
</code></pre>
<h4 id="应用场景-5">应用场景</h4>
<ul>
<li><strong>需要稳定排序的场景</strong>:数据库多字段排序、UI列表渲染保持相同元素的原始顺序</li>
<li><strong>外部排序</strong>:海量数据无法一次装入内存时,分块排序再合并,天然适合归并思想</li>
<li><strong>链表排序</strong>:链表上做归并排序不需要额外空间(不需要随机访问),比快速排序更合适</li>
<li><strong>Python / Java标准库</strong>:Python的<code>sorted()</code>和Java的<code>Collections.sort()</code>底层均使用 Timsort(归并 + 插入)</li>
</ul>
<h4 id="复杂度-5">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n log n)</td>
<td>O(n log n)</td>
<td>O(n log n)</td>
<td>O(n)</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>归并排序是唯一一个在最好、平均、最坏情况下都保持O(n log n)且稳定的比较排序算法,代价是需要额外空间。这就是算法设计中经典的<strong>时间-空间权衡</strong>。</p>
</blockquote>
<hr>
<h3 id="7-堆排序heap-sort-树形选择">7. 堆排序(Heap Sort)— 树形选择</h3>
<p><strong>算法原理</strong>:利用堆(完全二叉树)这种数据结构来排序。先把数组构建成一个大顶堆(每个父节点都大于等于子节点),然后不断取出堆顶(最大值)交换到数组末尾,并对剩余元素重新堆化处理,直到全部有序。</p>
<p>堆的精妙之处在于:利用的是完全二叉树的结构,用数组直接表示,父子节点关系通过索引计算完成。对于位置i的元素,其左子节点在2i+1,右子节点在2i+2,父节点在(i-1)/2,无需额外指针。</p>
<ol>
<li>先把数组构建成大顶堆(每个父节点 ≥ 子节点)。</li>
<li>每次取出堆顶(最大值)放到数组末尾,然后缩小堆的范围并重新调整堆,使剩余元素依然保持大顶堆性质。</li>
<li>重复上述步骤,直到所有元素排序完成。</li>
</ol>
<blockquote>
<p><strong>生活类比</strong>:就像整理一堆水果,把最大的放在顶上,每次取出最顶上的水果放到盘子里,然后让剩下的水果重新“自动堆成一座山”,下一次再取最大的。不断重复,最终就把所有水果从大到小排好。</p>
</blockquote>
<h4 id="流程图-6">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 15, 'rankSpacing': 25, 'padding': 20}}}%%
graph LR
    S(["开始"]) --&gt; BUILD["构建大顶堆\n从最后一个非叶节点\n到根逐个下沉"]
    BUILD --&gt; LOOP{"未排序部分\n长度 &gt; 1 ?"}
    LOOP --&gt;|"否"| END(["排序完成"])
    LOOP --&gt;|"是"| SWAP["交换堆顶与\n未排序部分末尾"]
    SWAP --&gt; SHRINK["未排序范围 - 1"]
    SHRINK --&gt; HEAPIFY["对堆顶执行下沉\nsift down"]
    HEAPIFY --&gt; LOOP

    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    class S,END start
    class LOOP decision
    class BUILD,SWAP,SHRINK,HEAPIFY process
</div><h4 id="伪代码-6">伪代码</h4>
<pre><code class="language-js">function HeapSort(arr):
    n = length(arr)
    // 建堆:从最后一个非叶节点开始,自底向上堆化
    for i = n/2 - 1 downto 0:
      SiftDown(arr, i, n)

    // 排序:反复取堆顶放到末尾
    for i = n - 1 downto 1:
      swap(arr, arr)                // 堆顶(最大值)放到末尾
      SiftDown(arr, 0, i)               // 对剩余元素重新堆化

function SiftDown(arr, parent, size):
    while 2 * parent + 1 &lt; size:            // 还有子节点
      child = 2 * parent + 1            // 左子节点
      if child + 1 &lt; size and arr &gt; arr:
            child = child + 1               // 选较大的子节点
      if arr &gt;= arr:
            break                           // 父节点已经最大,停止
      swap(arr, arr)       // 下沉
      parent = child
</code></pre>
<h4 id="应用场景-6">应用场景</h4>
<ul>
<li><strong>优先队列调度</strong>:操作系统进程调度、网络包调度底层均基于堆结构</li>
<li><strong>大规模 Top‑K 问题</strong>:从10亿级数据中取前K个值,只需维护大小为K的小顶堆,复杂度 O (n log k)</li>
<li><strong>内存受限环境</strong>:原地排序 O (1) 空间,最坏复杂度稳定 O (n log n)</li>
<li><strong>高性能定时器</strong>:Nginx、Go Runtime、Redis 均使用堆管理定时器</li>
</ul>
<h4 id="复杂度-6">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n log n)</td>
<td>O(n log n)</td>
<td>O(n log n)</td>
<td>O(1)</td>
<td>不稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>堆排序理论上非常理想——原地排序且时间复杂度稳定为 O(n log n),但在实际应用中通常比快速排序慢。原因在于堆化操作需要在数组中频繁跳跃访问父子节点,父节点和子节点在内存中相距较远,CPU 缓存命中率低,导致效率下降。</p>
</blockquote>
<hr>
<h3 id="8-计数排序counting-sort-用空间换时间">8. 计数排序(Counting Sort)— 用空间换时间</h3>
<p><strong>算法原理</strong>:统计每个元素出现的次数,用额外数组记录到对应下标,再按顺序输出,实现排序,不进行元素比较。属于非比较排序,适合元素范围不大、为整数的数组,时间复杂度 O(n + k)。</p>
<blockquote>
<p><strong>生活类比</strong>: 就像统计考试分数:准备一个 0–100 的计数表,遍历所有试卷,把每个分数出现的次数加 1。最后从 0 分到 100 分依次输出,每个分数出现几次就写几次,这样就得到排序好的成绩单。</p>
</blockquote>
<h4 id="流程图-7">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 10, 'rankSpacing': 20, 'padding': 15}}}%%
graph LR
    S(["开始"]) --&gt; RANGE["找到最大值 max\n创建计数数组 count"]
    RANGE --&gt; COUNT["遍历数组\n统计每个元素出现次数"]
    COUNT --&gt; PREFIX["对 count 做前缀和\n确定每个元素的位置"]
    PREFIX --&gt; FILL["反向遍历原数组\n按 count 放入输出数组"]
    FILL --&gt; END(["排序完成"])

    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px


    class S,END start
    class RANGE,COUNT,PREFIX,FILL process
</div><h4 id="伪代码-7">伪代码</h4>
<pre><code class="language-js">function CountingSort(arr):
    max_val = max(arr)                      // 找到最大值
    count = array of (max_val + 1) zeros    // 创建计数数组

    for x in arr:                           // 统计每个值的出现次数
      count = count + 1

    for i = 1 to max_val:                   // 前缀和:count变为"&lt;=i的元素总数"
      count = count + count

    output = array of length(arr)
    for i = length(arr) - 1 downto 0:       // 反向遍历保证稳定性
      output] - 1] = arr
      count] = count] - 1

    return output
</code></pre>
<h4 id="应用场景-7">应用场景</h4>
<ul>
<li><strong>年龄排序</strong>:范围 0–150 固定且集中,计数排序可实现极致线性效率</li>
<li><strong>考试成绩排序</strong>:分数 0–100 范围有限,无需比较即可完成排序与统计</li>
<li><strong>字符频率统计</strong>:ASCII 字符集 0–127,非常适合计数排序</li>
<li><strong>基数排序的子过程</strong>:基数排序每一位的稳定排序通常由计数排序实现</li>
</ul>
<h4 id="复杂度-7">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n + k)</td>
<td>O(n + k)</td>
<td>O(n + k)</td>
<td>O(n + k)</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>计数排序高效、直接,不依赖比较,尤其适合数值范围有限的数据,就像统计考试分数一样,是一种巧妙且实用的排序方法。</p>
</blockquote>
<hr>
<h3 id="9-基数排序radix-sort-逐位排序">9. 基数排序(Radix Sort)— 逐位排序</h3>
<p><strong>算法原理</strong>:不直接比较数字大小,而是按个位、十位、百位……逐位处理。每一位使用稳定排序(通常是计数排序),保证低位顺序在高位处理时不被破坏,最终得到完整有序的序列。属于非比较排序,适合整数且位数有限的数组。</p>
<blockquote>
<p><strong>生活类比</strong>:就像整理邮政编码的信件,先按最后一位数字分堆,再按倒数第二位分堆……逐位处理,直到按完整邮编顺序排列好所有信件。</p>
</blockquote>
<h4 id="流程图-8">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 15, 'rankSpacing': 25, 'padding': 20}}}%%
graph LR
    S(["开始"]) --&gt; MAXD["计算数组&lt;br&gt;最大位数 d"]
    MAXD --&gt; DINIT["digit = 1\n从最低位开始"]
    DINIT --&gt; DCHK{"digit ≤ d ? &lt;br&gt;(最低位 → 最高位)"}
    DCHK --&gt;|"否"| END(["排序完成"])
    DCHK --&gt;|"是"| STABLE["对数组 a\n按 digit 位进行稳定排序\n(计数排序)"]
    STABLE --&gt; NEXT["digit++"]
    NEXT --&gt; |"处理下一位"| DCHK

    %% 节点样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    %% 应用样式
    class S,END start
    class DCHK decision
    class MAXD,DINIT,STABLE,NEXT process
</div><h4 id="伪代码-8">伪代码</h4>
<pre><code class="language-js">function RadixSort(arr):
    max_val = max(arr)
    d = number_of_digits(max_val)         // 最大位数

    exp = 1                                 // 当前位的权重:1, 10, 100, ...
    for digit = 1 to d:
      CountingSortByDigit(arr, exp)       // 按当前位做稳定排序
      exp = exp * 10

function CountingSortByDigit(arr, exp):
    count = array of 10 zeros               // 0-9 十个桶
    output = array of length(arr)

    for x in arr:
      digit = (x / exp) % 10             // 取出当前位
      count++

    for i = 1 to 9:
      count += count            // 前缀和

    for i = length(arr) - 1 downto 0:       // 反向遍历保证稳定性
      digit = (arr / exp) % 10
      output - 1] = arr
      count--

    copy output to arr
</code></pre>
<h4 id="应用场景-8">应用场景</h4>
<ul>
<li><strong>手机号排序</strong>:11 位定长数字,效率 O (11n),远快于 O (n log n)</li>
<li><strong>IP 地址排序</strong>:4 段固定格式数字,天然适合逐段排序</li>
<li><strong>身份证号排序</strong>:定长数字编码,规则统一,可实现线性时间排序</li>
<li><strong>大规模整数 / 定长字符串</strong>:位数固定时,O (dn) 接近线性,海量数据下优势巨大</li>
</ul>
<h4 id="复杂度-8">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n × d)</td>
<td>O(n × d)</td>
<td>O(n × d)</td>
<td>O(n + k)</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>基数排序不依赖比较,高效处理大规模整数或固定长度字符串,就像邮局分拣信件一样,是实践中验证过的排序智慧。</p>
</blockquote>
<hr>
<h3 id="10-桶排序bucket-sort-分桶映射">10. 桶排序(Bucket Sort)— 分桶映射</h3>
<p><strong>算法原理</strong>:将数据按数值区间划分成若干桶,每个桶内部单独排序(通常用插入排序),然后按桶的顺序依次合并。效率依赖于数据均匀分布,每个桶元素较少时,总体性能最佳。属于非比较排序,适合分布较均匀的数值数组。</p>
<blockquote>
<p><strong>生活类比</strong>:就像收集水果,把苹果按大小或颜色放到不同的篮子里,每个篮子里再整理一下,最后按篮子顺序排列所有苹果,就得到整齐的果堆。</p>
</blockquote>
<h4 id="流程图-9">流程图</h4>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 15, 'rankSpacing': 35, 'padding': 20}}}%%
graph LR
    S(["开始"]) --&gt; INIT["创建 k 个空桶\n确定值域范围"]

    INIT --&gt; DIST["遍历数组\n将每个元素&lt;br&gt;分配到对应桶"]

    DIST --&gt; SORT["对每个非空桶\n内部排序"]

    SORT --&gt; CONCAT["按桶顺序\n依次拼接所有元素"]

    CONCAT --&gt; NEXT{"还有未处理的桶 ?"}
    NEXT --&gt;|"是,处理下一桶"| SORT
    NEXT --&gt;|"否"| END(["排序完成"])

    %% 样式
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px
    classDef decision fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px
    classDef process fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px

    class S,END start
    class NEXT decision
    class INIT,DIST,SORT,CONCAT process
</div><h4 id="伪代码-9">伪代码</h4>
<pre><code class="language-js">function BucketSort(arr):
    n = length(arr)
    min_val = min(arr)
    max_val = max(arr)
    bucket_count = n                        // 桶数量通常取n
    bucket_size = (max_val - min_val + 1) / bucket_count

    buckets = array of bucket_count empty lists

    for x in arr:                           // 将元素分配到桶中
      idx = (x - min_val) / bucket_size
      buckets.append(x)

    for each bucket in buckets:             // 桶内排序(通常用插入排序)
      InsertionSort(bucket)

    result = concatenate all buckets      // 按桶顺序拼接
    return result
</code></pre>
<h4 id="应用场景-9">应用场景</h4>
<ul>
<li><strong>均匀分布浮点数排序</strong>:如 0~1 随机浮点数,分桶后接近线性时间</li>
<li><strong>分段统计排序</strong>:成绩分段、商品价格区间、用户年龄段归类等业务场景</li>
<li><strong>图像颜色直方图</strong>:按像素值分桶统计,是图像处理经典应用</li>
<li><strong>请求负载均衡</strong>:按特征值分桶分发流量,实现均匀调度与分流</li>
</ul>
<h4 id="复杂度-9">复杂度</h4>
<table>
<thead>
<tr>
<th>最好</th>
<th>平均</th>
<th>最坏</th>
<th>空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(n + k)</td>
<td>O(n + k)</td>
<td>O(n²)</td>
<td>O(n + k)</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<blockquote>
<p>桶排序适合均匀分布的数据,高效且直观,就像按篮子整理水果一样,是实践中处理特定场景的稳定排序方法。</p>
</blockquote>
<hr>
<h2 id="四10大排序算法特点回顾">四、10大排序算法特点回顾</h2>
<table>
<thead>
<tr>
<th>算法</th>
<th>平均时间复杂度</th>
<th>最坏时间复杂度</th>
<th>空间复杂度</th>
<th>稳定性</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>冒泡排序</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>稳定</td>
<td>相邻元素两两比较,最大或最小元素逐轮“冒”到最后</td>
</tr>
<tr>
<td>选择排序</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>不稳定</td>
<td>每轮选择未排序里最小(或最大)元素放到已排序末尾</td>
</tr>
<tr>
<td>插入排序</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>稳定</td>
<td>类似打扑克,从未排序中取元素插入到已排序序列中</td>
</tr>
<tr>
<td>快速排序</td>
<td>O(n log n)</td>
<td>O(n²)</td>
<td>O(log n)</td>
<td>不稳定</td>
<td>分治+分区,选基准元素将数组拆分,递归排序左右区间</td>
</tr>
<tr>
<td>归并排序</td>
<td>O(n log n)</td>
<td>O(n log n)</td>
<td>O(n)</td>
<td>稳定</td>
<td>递归拆分数组到单个元素,再不断向上合并两个有序子数组</td>
</tr>
<tr>
<td>堆排序</td>
<td>O(n log n)</td>
<td>O(n log n)</td>
<td>O(1)</td>
<td>不稳定</td>
<td>利用大顶堆选择最大元素放到末尾,原地排序</td>
</tr>
<tr>
<td>希尔排序</td>
<td>O(n^1.3)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>不稳定</td>
<td>分组式插入排序,先大步长分组排序再逐步缩小步长</td>
</tr>
<tr>
<td>计数排序</td>
<td>O(n + k)</td>
<td>O(n + k)</td>
<td>O(n + k)</td>
<td>稳定</td>
<td>不比较元素大小,统计每个值出现的次数并直接输出</td>
</tr>
<tr>
<td>基数排序</td>
<td>O(n × d)</td>
<td>O(n × d)</td>
<td>O(n + k)</td>
<td>稳定</td>
<td>按位从低到高分别排序,最后从高到低合并数据</td>
</tr>
<tr>
<td>桶排序</td>
<td>O(n + k)</td>
<td>O(n²)</td>
<td>O(n + k)</td>
<td>稳定</td>
<td>将数据分成若干桶,桶内排序后再将全部桶合并</td>
</tr>
</tbody>
</table>
<blockquote>
<p>10大排序算法的多语言实现(C/Java/Go/Python/JS/TS/Rust)源码地址:https://github.com/microwind/algorithms</p>
</blockquote>
<h2 id="五ai时代如何指导ai选择排序算法">五、AI时代,如何指导AI选择排序算法?</h2>
<p>前面回顾了10大排序算法的特点与适用场景,现在回到文章开头的问题:</p>
<blockquote>
<p>AI可以轻松生成算法代码,那作为程序员,我们还有必要学习算法吗?</p>
</blockquote>
<p>AI时代,我们无需手写代码了,但需要理解算法背后的思想。只有这样才能<strong>根据实际场景,指导AI做出合适的选择</strong>。</p>
<p>下面看下AI引起的编程变革。</p>
<h3 id="1-编程方式发生了本质变化">1. 编程方式发生了本质变化</h3>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 45, 'padding': 20}}}%%
graph LR
    PAST(["过去&lt;br/&gt;人工编写排序代码"]):::past
    NOW(["现在&lt;br/&gt;人工定义策略 + 约束&lt;br/&gt;指导AI选择算法"]):::now
    FUTURE(["将来&lt;br/&gt;人工只描述目标&lt;br/&gt;AI自主决策与执行"]):::future

    PAST --&gt; NOW --&gt; FUTURE

    classDef past fill:#615B5B,stroke:#282A2D,color:#ffffff,stroke-width:2px,rx:12,ry:12
    classDef now fill:#6a5acd,color:#fff,stroke:#483d8b,stroke-width:2px,rx:14,ry:14
    classDef future fill:#11908A,color:#fff,stroke:#008080,stroke-width:2px,rx:14,ry:14
</div><blockquote>
<p><strong>过去:</strong> 程序员编写排序代码<br>
<strong>现在:</strong> 人工定义算法策略(需求拆解)和约束条件(性能/稳定性/成本), 指导AI实现<br>
<strong>未来:</strong> 人只告诉AI需求目标,AI自主确定策略,自主执行合适方案</p>
</blockquote>
<h3 id="2-ai编程的发展阶段">2. AI编程的发展阶段</h3>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 30, 'rankSpacing': 40, 'padding': 10}}}%%
graph LR

    %% ===== 时间线主线 =====
    P(["2023以前&lt;br/&gt;传统模式"]) --&gt; Q(["2023-2024&lt;br/&gt;Copilot模式"]) --&gt; R(["2025+&lt;br/&gt;Agent模式"]) --&gt; S(["2026+&lt;br/&gt;Agentic模式"])

    %% ===== 核心能力纵向显示 =====
    P1("手写代码&lt;br/&gt;实现功能&lt;br/&gt;角色:执行者")
    Q1("L1 AI Copilot&lt;br/&gt;辅助编码&lt;br/&gt;角色:增强执行")
    R1("L2 AI Agent&lt;br/&gt;指导AI&lt;br/&gt;角色:指挥者")
    S1("L3 Agentic AI&lt;br/&gt;驱动AI&lt;br/&gt;角色:决策者")

    P --&gt; P1
    Q --&gt; Q1
    R --&gt; R1
    S --&gt; S1

    %% ===== 主节点 =====
    style P fill:#615B5B,stroke:#282A2D,color:#ffffff,stroke-width:2px,rx:12,ry:12
    style Q fill:#FF6253,stroke:#c94c4c,color:#ffffff,stroke-width:2px,rx:12,ry:12
    style R fill:#6a5acd,stroke:#1D4ED8,color:#ffffff,stroke-width:2px,rx:12,ry:12
    style S fill:#11908A,stroke:#15803D,color:#ffffff,stroke-width:2px,rx:12,ry:12

    %% ===== 子节点 =====
    style P1 fill:#E6E8EB,stroke:#90959D,color:#15181E,rx:10,ry:10
    style Q1 fill:#FDE2DF,stroke:#F59E0B,color:#78350F,rx:10,ry:10
    style R1 fill:#F2E7FA,stroke:#7C04AB,color:#1E3A8A,rx:10,ry:10
    style S1 fill:#C2F8F1,stroke:#23B659,color:#14532D,rx:10,ry:10
</div><h3 id="3-先分析需求和选择策略再让ai实现代码">3. 先分析需求和选择策略,再让AI实现代码</h3>
<ul>
<li><strong>先明确约束</strong>:根据数据规模、稳定性要求、内存限制、键值范围与分布特征,确定算法边界</li>
<li><strong>通用场景首选</strong>:普通数组优先使用快速排序;链表或需要稳定排序时选择归并排序</li>
<li><strong>小规模/近乎有序数据</strong>:插入排序效果最好;也可用“无交换提前结束”的冒泡作为简单有序性检测</li>
<li><strong>整数且范围有限</strong>:计数排序、基数排序、桶排序可将时间复杂度降至线性级别,性能优势极大</li>
<li><strong>最坏情况防护</strong>:快排应使用随机或三数取中选基准,小分区切换插入排序;大量重复元素时使用三路划分</li>
</ul>
<div class="mermaid">%%{init: {'flowchart': {'nodeSpacing': 30, 'rankSpacing': 15, 'padding': 10}}}%%
graph TB
    START(["需要排序"]):::start --&gt; Q1{"数据规模?"}

    Q1 --&gt;|"小规模 n &lt; 50"| A1(["插入排序"]):::small
    Q1 --&gt;|"小规模 n &lt; 50"| A1b(["冒泡排序"]):::small
    Q1 --&gt;|"小规模 n &lt; 50"| A1c(["选择排序"]):::small

    Q1 --&gt;|"中等/大规模"| Q2{"数据特征?"}

    Q2 --&gt;|"整数且范围有限"| A2(["计数排序"]):::noncmp
    Q2 --&gt;|"多位整数/定长字符串"| A3(["基数排序"]):::noncmp
    Q2 --&gt;|"均匀分布"| A4(["桶排序"]):::noncmp
    Q2 --&gt;|"通用数据"| Q3{"额外要求?"}

    Q3 --&gt;|"需要稳定"| A5(["归并排序"]):::cmp
    Q3 --&gt;|"内存受限"| A6(["堆排序"]):::cmp
    Q3 --&gt;|"追求速度"| A7(["快速排序"]):::cmp
    Q3 --&gt;|"近乎有序"| A8(["插入排序 / 希尔排序"]):::cmp

    %% 样式定义(统一圆角 + 分层颜色)
    classDef start fill:#FF6253,color:#fff,stroke:#c94c4c,stroke-width:2px,rx:12,ry:12
    classDef question fill:#0f3460,color:#fff,stroke:#0f69a1,stroke-width:2px,rx:10,ry:10
    classDef small fill:#DCA8FA,stroke:#483d8b,stroke:#7913CC,rx:10,ry:10
    classDef cmp fill:#51D0EA,color:#052e16,stroke:#15807C,rx:10,ry:10
    classDef noncmp fill:#f59e0b,color:#422006,stroke:#b45309,rx:10,ry:10

    class START start
    class Q1,Q2,Q3 question
</div><h3 id="4-实际场景多采用混合策略">4. 实际场景多采用混合策略</h3>
<p><strong>编程语言中,会根据数据规模、分布特征和运行时情况,动态组合多种算法</strong></p>
<ul>
<li><strong>C 标准库 (<code>qsort</code>)</strong>:QuickSort 变种 + 小数组插入排序优化</li>
<li><strong>C++ STL (<code>std::sort</code>)</strong>:Introsort(快速排序 + 堆排序 + 插入排序)</li>
<li><strong>Java (<code>Arrays.sort</code>)</strong>:双轴 QuickSort(基本类型)、Timsort(对象数组,归并 + 插入排序)</li>
<li><strong>Python (<code>sorted</code> / <code>list.sort</code>)</strong>:Timsort(归并 + 插入排序)</li>
<li><strong>Go (<code>sort</code> 包)</strong>:PDQSort + 插入排序</li>
<li><strong>JavaScript (<code>Array.prototype.sort</code>)</strong>:Timsort / QuickSort + 插入排序</li>
<li><strong>Rust (<code>slice::sort</code> / <code>sort_unstable</code>)</strong>:<code>sort</code> → Timsort 变体;<code>sort_unstable</code> → PDQSort(快排优化版)</li>
</ul>
<p><strong>开源软件与系统中,也会根据数据特点与内存动态选择排序策略</strong></p>
<ul>
<li>
<p><strong>MySQL</strong>:内存内 ORDER BY 使用快速排序,超 <code>sort_buffer_size</code> 后切换为外部归并排序</p>
</li>
<li>
<p><strong>MongoDB</strong>:内存排序 QuickSort/Timsort,结果集 &gt;32 MB 时自动降级为外部归并排序 (<code>allowDiskUse</code>)</p>
</li>
<li>
<p><strong>Elasticsearch (Lucene)</strong>:多段合并归并排序;<code>doc_values</code> 字段用计数排序或快速排序变体</p>
</li>
<li>
<p><strong>PostgreSQL</strong>:内存排序 QuickSort,超过 <code>work_mem</code> 后切换外部归并</p>
</li>
<li>
<p><strong>Redis</strong>:小数据量 QuickSort,大数据量归并排序,支持外部临时文件</p>
</li>
<li>
<p><strong>Spark / Flink</strong>:分区内用 Timsort,跨分区全局排序用采样+范围划分,流式 Top‑N 用堆</p>
</li>
<li>
<p><strong>推荐系统 Top‑N</strong>:召回用近似排序(ANN/桶/堆),精排用快速排序或归并排序,整体用堆维护动态 Top‑K</p>
</li>
<li>
<p><strong>实时计算(Flink SQL)</strong>:流式 Top‑N 用堆排序,批处理超内存用外部归并排序</p>
</li>
</ul>
<hr>
<h2 id="六总结">六、总结</h2>
<p>10大排序算法,从朴素的冒泡、选择、插入,到基于分治思想的快速排序、归并排序,再到利用数据特征实现线性时间的计数、基数和桶排序,每一种都体现了前人解决问题的智慧。</p>
<p>在实际应用中,我们经常要做出判断:何时优先选择快排,何时切换到线性时间的计数/基数/桶排序,何时为了稳定性选择归并,这些都取决于数据特征与约束条件。</p>
<p>AI时代,实现排序算法本身已经不重要了,重要的是<strong>选择哪种算法、为什么这么选</strong>。理解这些算法思想,指导AI时我们才会从容不迫。</p>
<hr>
<h3 id="相关链接">相关链接</h3>
<ul>
<li>程序员必备的算法思想指南</li>
<li>程序员如何成为算法思想工程师</li>
<li>程序员如何成为Agent工程师</li>
</ul><br><br>
来源:https://www.cnblogs.com/jarryli/p/19777122
頁: [1]
查看完整版本: AI时代的算法思维:10大经典排序学习