<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>里肌肉實驗室</title>
  
  
  <link href="https://boycework.eu.cc/atom.xml" rel="self"/>
  
  <link href="https://boycework.eu.cc/"/>
  <updated>2026-03-09T15:08:23.690Z</updated>
  <id>https://boycework.eu.cc/</id>
  
  <author>
    <name>李柏毅 BoyceLab</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>演算法筆記：八大排序演算法</title>
    <link href="https://boycework.eu.cc/2026/03/05/sorting-algorithms/"/>
    <id>https://boycework.eu.cc/2026/03/05/sorting-algorithms/</id>
    <published>2026-03-05T14:42:00.000Z</published>
    <updated>2026-03-09T15:08:23.690Z</updated>
    
    <content type="html"><![CDATA[<!-- 頂部互動區：點擊演算法演示並跳轉 --><div class="cyber-sorting-lab">  <div class="console-header">    <div class="dots"><span></span><span></span><span></span></div>    <div class="console-title">SORTING_MATRIX_SIMULATOR_v2.0 // 點擊演算法後按鈕跳轉說明</div>  </div>  <div class="algo-nav-bar">    <button class="algo-nav-btn active" data-algo="bubble" onclick="selectAlgo('bubble')">Bubble O(n²)</button>    <button class="algo-nav-btn" data-algo="selection" onclick="selectAlgo('selection')">Selection O(n²)</button>    <button class="algo-nav-btn" data-algo="insertion" onclick="selectAlgo('insertion')">Insertion O(n²)</button>    <button class="algo-nav-btn accent" data-algo="merge" onclick="selectAlgo('merge')">Merge O(n log n)</button>    <button class="algo-nav-btn accent" data-algo="quick" onclick="selectAlgo('quick')">Quick O(n log n)</button>    <button class="algo-nav-btn accent" data-algo="heap" onclick="selectAlgo('heap')">Heap O(n log n)</button>    <button class="algo-nav-btn green" data-algo="counting" onclick="selectAlgo('counting')">Counting O(n+k)</button>  </div>  <div class="controls">    <button class="cyber-btn" id="btn-generate"><i class="fa-solid fa-rotate"></i> 新資料</button>    <button class="cyber-btn primary" id="btn-run"><i class="fa-solid fa-play"></i> 執行</button>    <button class="cyber-btn danger" id="btn-stop" disabled><i class="fa-solid fa-stop"></i> 終止</button>    <label class="cyber-btn sound-toggle" id="sound-label">      <input type="checkbox" id="sound-check" checked style="display:none">      <i class="fa-solid fa-volume-high"></i> 音效 ON    </label>  </div>  <div class="status-panel" id="status-panel">System Standby... // Choose an Algorithm</div>  <div class="array-container" id="array-container"></div>  <div class="jump-section-btn-wrap">    <a id="jump-section-link" href="#bubble-section" class="cyber-btn primary">📖 跳轉到 Bubble Sort 說明</a>  </div></div><span id="more"></span><hr><h2 id="什麼是-Big-O-時間複雜度"><a href="#什麼是-Big-O-時間複雜度" class="headerlink" title="什麼是 Big O (時間複雜度)?"></a>什麼是 Big O (時間複雜度)?</h2><p>Big O 描述演算法在<strong>資料量 n 趨近無限大</strong>時的效能趨勢。</p><table><thead><tr><th>演算法</th><th>平均</th><th>最差</th><th>空間</th><th>穩定</th></tr></thead><tbody><tr><td><strong>Bubble Sort</strong></td><td>O(n²)</td><td>O(n²)</td><td>O(1)</td><td>✅</td></tr><tr><td><strong>Selection Sort</strong></td><td>O(n²)</td><td>O(n²)</td><td>O(1)</td><td>❌</td></tr><tr><td><strong>Insertion Sort</strong></td><td>O(n²)</td><td>O(n²)</td><td>O(1)</td><td>✅</td></tr><tr><td><strong>Merge Sort</strong></td><td>O(n log n)</td><td>O(n log n)</td><td>O(n)</td><td>✅</td></tr><tr><td><strong>Quick Sort</strong></td><td>O(n log n)</td><td>O(n²)</td><td>O(log n)</td><td>❌</td></tr><tr><td><strong>Heap Sort</strong></td><td>O(n log n)</td><td>O(n log n)</td><td>O(1)</td><td>❌</td></tr><tr><td><strong>Counting Sort</strong></td><td>O(n+k)</td><td>O(n+k)</td><td>O(k)</td><td>✅</td></tr><tr><td><strong>Radix Sort</strong></td><td>O(nk)</td><td>O(nk)</td><td>O(n+k)</td><td>✅</td></tr></tbody></table><hr><h2 id="🔴-Bubble-Sort（氣泡排序）"><a href="#🔴-Bubble-Sort（氣泡排序）" class="headerlink" title="🔴 Bubble Sort（氣泡排序）"></a><span id="bubble-section">🔴 Bubble Sort（氣泡排序）</span></h2><p><strong>核心思想</strong>：每輪把相鄰的兩個元素互比，如果左邊比較大就交換。這樣最大數字會像泡泡一樣「浮」到最右端，然後一直縮小範圍重複這個動作。</p><p><strong>什麼時候用？</strong> 幾乎不用於實戰，但因為邏輯最直覺，是學習演算法的起點。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bubbleSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="type">bool</span> swapped = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n - i - <span class="number">1</span>; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &gt; arr[j + <span class="number">1</span>]) &#123;</span><br><span class="line">                <span class="built_in">swap</span>(arr[j], arr[j + <span class="number">1</span>]);</span><br><span class="line">                swapped = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!swapped) <span class="keyword">break</span>; <span class="comment">// 優化：提早結束</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">64</span>, <span class="number">34</span>, <span class="number">25</span>, <span class="number">12</span>, <span class="number">22</span>, <span class="number">11</span>, <span class="number">90</span>&#125;;</span><br><span class="line">    <span class="built_in">bubbleSort</span>(v);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Output: 11 12 22 25 34 64 90</span></span><br></pre></td></tr></table></figure><hr><h2 id="🔴-Selection-Sort（選擇排序）"><a href="#🔴-Selection-Sort（選擇排序）" class="headerlink" title="🔴 Selection Sort（選擇排序）"></a><span id="selection-section">🔴 Selection Sort（選擇排序）</span></h2><p><strong>核心思想</strong>：每一輪從「尚未排序的部分」找出<strong>最小值</strong>，然後跟最前面的元素交換。每輪確定一個位置正確的元素。</p><p><strong>注意</strong>：Selection Sort 永遠執行 O(n²) 次比較，<strong>無論資料本身是否已排序</strong>，這是它的最大缺點。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">selectionSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="type">int</span> minIdx = i;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = i + <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &lt; arr[minIdx]) minIdx = j;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">swap</span>(arr[i], arr[minIdx]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🔴-Insertion-Sort（插入排序）"><a href="#🔴-Insertion-Sort（插入排序）" class="headerlink" title="🔴 Insertion Sort（插入排序）"></a><span id="insertion-section">🔴 Insertion Sort（插入排序）</span></h2><p><strong>核心思想</strong>：想像左手拿著已排好的牌，右手每次從右邊抽一張，然後插進左手牌堆的正確位置。</p><p><strong>什麼時候用？</strong> 資料已接近排序完成時（nearly sorted），時間複雜度接近 O(n)！在 <code>std::sort</code> 的 IntroSort 實作中，當子陣列縮到小於 16 個元素時，會切換為 Insertion Sort。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insertionSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> key = arr[i];</span><br><span class="line">        <span class="type">int</span> j = i - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (j &gt;= <span class="number">0</span> &amp;&amp; arr[j] &gt; key) &#123;</span><br><span class="line">            arr[j + <span class="number">1</span>] = arr[j];</span><br><span class="line">            j--;</span><br><span class="line">        &#125;</span><br><span class="line">        arr[j + <span class="number">1</span>] = key;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🟡-Merge-Sort（合併排序）"><a href="#🟡-Merge-Sort（合併排序）" class="headerlink" title="🟡 Merge Sort（合併排序）"></a><span id="merge-section">🟡 Merge Sort（合併排序）</span></h2><p><strong>核心思想</strong>：<strong>分治法 (Divide and Conquer)</strong>. 不斷把陣列對半切，直到每個子陣列只剩 1 個元素。然後再一一將兩個有序陣列「合併」成一個有序陣列。</p><p><strong>什麼時候用？</strong> 需要<strong>穩定排序</strong>且時間複雜度穩定在 O(n log n) 的場合，例如處理鏈結串列、外部排序（待排資料超過記憶體大小）。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">merge</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> l, <span class="type">int</span> m, <span class="type">int</span> r)</span> </span>&#123;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">L</span><span class="params">(arr.begin() + l, arr.begin() + m + <span class="number">1</span>)</span></span>;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">R</span><span class="params">(arr.begin() + m + <span class="number">1</span>, arr.begin() + r + <span class="number">1</span>)</span></span>;</span><br><span class="line">    <span class="type">int</span> i = <span class="number">0</span>, j = <span class="number">0</span>, k = l;</span><br><span class="line">    <span class="keyword">while</span> (i &lt; L.<span class="built_in">size</span>() &amp;&amp; j &lt; R.<span class="built_in">size</span>()) &#123;</span><br><span class="line">        <span class="keyword">if</span> (L[i] &lt;= R[j]) arr[k++] = L[i++];</span><br><span class="line">        <span class="keyword">else</span> arr[k++] = R[j++];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (i &lt; L.<span class="built_in">size</span>()) arr[k++] = L[i++];</span><br><span class="line">    <span class="keyword">while</span> (j &lt; R.<span class="built_in">size</span>()) arr[k++] = R[j++];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">mergeSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> l, <span class="type">int</span> r)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (l &gt;= r) <span class="keyword">return</span>;</span><br><span class="line">    <span class="type">int</span> m = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">    <span class="built_in">mergeSort</span>(arr, l, m);</span><br><span class="line">    <span class="built_in">mergeSort</span>(arr, m + <span class="number">1</span>, r);</span><br><span class="line">    <span class="built_in">merge</span>(arr, l, m, r);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">38</span>, <span class="number">27</span>, <span class="number">43</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">82</span>, <span class="number">10</span>&#125;;</span><br><span class="line">    <span class="built_in">mergeSort</span>(v, <span class="number">0</span>, v.<span class="built_in">size</span>() - <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🟡-Quick-Sort（快速排序）"><a href="#🟡-Quick-Sort（快速排序）" class="headerlink" title="🟡 Quick Sort（快速排序）"></a><span id="quick-section">🟡 Quick Sort（快速排序）</span></h2><p><strong>核心思想</strong>：挑一個 <strong>Pivot（基準點）</strong>，把所有比 Pivot 小的元素放到左邊，比 Pivot 大的放到右邊。然後遞迴對左右兩側重複這個動作。</p><p><strong>什麼時候用？</strong> 這是實際最常用的排序法。<code>std::sort</code> 的底層 (IntroSort) 即是以 Quick Sort 為核心。平均 O(n log n)，且記憶體使用效率高（In-place）。</p><blockquote><p><strong>⚠️ 坑點</strong>：如果每次都選最小&#x2F;最大值當 Pivot，退化成 O(n²)。實務上要用<strong>亂數選 Pivot</strong> 或<strong>三數取中</strong>來避免。</p></blockquote><p><strong>C++ 競程寫法（推薦用 <code>std::sort</code>）：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 競程直接用 std::sort，底層已是 IntroSort (Quick+Heap+Insertion)</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">10</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">1</span>, <span class="number">5</span>&#125;;</span><br><span class="line">    <span class="built_in">sort</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>()); <span class="comment">// 升冪</span></span><br><span class="line">    <span class="built_in">sort</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), <span class="built_in">greater</span>&lt;<span class="type">int</span>&gt;()); <span class="comment">// 降冪</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 自訂排序</span></span><br><span class="line">    <span class="built_in">sort</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), [](<span class="type">int</span> a, <span class="type">int</span> b) &#123;</span><br><span class="line">        <span class="keyword">return</span> a % <span class="number">3</span> &lt; b % <span class="number">3</span>; <span class="comment">// 按模 3 餘數升冪</span></span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 手刻版本（理解用）</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">partition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> pivot = arr[high];</span><br><span class="line">    <span class="type">int</span> i = low - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = low; j &lt; high; j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arr[j] &lt; pivot) <span class="built_in">swap</span>(arr[++i], arr[j]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">swap</span>(arr[i + <span class="number">1</span>], arr[high]);</span><br><span class="line">    <span class="keyword">return</span> i + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">quickSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (low &lt; high) &#123;</span><br><span class="line">        <span class="type">int</span> pi = <span class="built_in">partition</span>(arr, low, high);</span><br><span class="line">        <span class="built_in">quickSort</span>(arr, low, pi - <span class="number">1</span>);</span><br><span class="line">        <span class="built_in">quickSort</span>(arr, pi + <span class="number">1</span>, high);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🟡-Heap-Sort（堆積排序）"><a href="#🟡-Heap-Sort（堆積排序）" class="headerlink" title="🟡 Heap Sort（堆積排序）"></a><span id="heap-section">🟡 Heap Sort（堆積排序）</span></h2><p><strong>核心思想</strong>：利用 <strong>Max-Heap（最大堆積）</strong> 這種資料結構。先把陣列建成 Max-Heap，然後每次把堆頂（最大值）取出放到末端，再對剩下的部分重新整理成 Heap，重複直到結束。</p><p><strong>什麼時候用？</strong> 當你需要 O(n log n) <strong>且</strong> O(1) 空間複雜度的穩定效能（Quick Sort 最差退化，Merge Sort 需要額外空間）。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 競程中 priority_queue 就是一個 Max-Heap</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    priority_queue&lt;<span class="type">int</span>&gt; maxHeap;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">3</span>, <span class="number">1</span>, <span class="number">4</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">9</span>, <span class="number">2</span>, <span class="number">6</span>&#125;;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) maxHeap.<span class="built_in">push</span>(x);</span><br><span class="line"></span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; sorted;</span><br><span class="line">    <span class="keyword">while</span> (!maxHeap.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        sorted.<span class="built_in">push_back</span>(maxHeap.<span class="built_in">top</span>());</span><br><span class="line">        maxHeap.<span class="built_in">pop</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">reverse</span>(sorted.<span class="built_in">begin</span>(), sorted.<span class="built_in">end</span>());</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : sorted) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 手刻 Heap Sort</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">heapify</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> n, <span class="type">int</span> i)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> largest = i, l = <span class="number">2</span>*i<span class="number">+1</span>, r = <span class="number">2</span>*i<span class="number">+2</span>;</span><br><span class="line">    <span class="keyword">if</span> (l &lt; n &amp;&amp; arr[l] &gt; arr[largest]) largest = l;</span><br><span class="line">    <span class="keyword">if</span> (r &lt; n &amp;&amp; arr[r] &gt; arr[largest]) largest = r;</span><br><span class="line">    <span class="keyword">if</span> (largest != i) &#123;</span><br><span class="line">        <span class="built_in">swap</span>(arr[i], arr[largest]);</span><br><span class="line">        <span class="built_in">heapify</span>(arr, n, largest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">heapSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = n/<span class="number">2</span><span class="number">-1</span>; i &gt;= <span class="number">0</span>; i--) <span class="built_in">heapify</span>(arr, n, i);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = n<span class="number">-1</span>; i &gt; <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="built_in">swap</span>(arr[<span class="number">0</span>], arr[i]);</span><br><span class="line">        <span class="built_in">heapify</span>(arr, i, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🔵-Counting-Sort（計數排序）"><a href="#🔵-Counting-Sort（計數排序）" class="headerlink" title="🔵 Counting Sort（計數排序）"></a><span id="counting-section">🔵 Counting Sort（計數排序）</span></h2><p><strong>核心思想</strong>：不靠「比較」來排序！先統計每個數字出現幾次，再依序填回陣列。</p><p><strong>限制條件</strong>：只適用於整數，且數值範圍 k 不能太大（k &#x3D; max - min），因為需要建立大小為 k 的計數陣列。</p><p><strong>什麼時候用？</strong> 數字範圍明確且較小時（例如成績 0<del>100、字元 0</del>255、APCS 中 N ≤ 10⁶ 且值域為 0~10⁶）可達到 O(n+k) 的速度，<strong>比比較式排序更快</strong>。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">countingSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (arr.<span class="built_in">empty</span>()) <span class="keyword">return</span>;</span><br><span class="line">    <span class="type">int</span> maxVal = *<span class="built_in">max_element</span>(arr.<span class="built_in">begin</span>(), arr.<span class="built_in">end</span>());</span><br><span class="line">    <span class="type">int</span> minVal = *<span class="built_in">min_element</span>(arr.<span class="built_in">begin</span>(), arr.<span class="built_in">end</span>());</span><br><span class="line">    <span class="type">int</span> range = maxVal - minVal + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">count</span><span class="params">(range, <span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : arr) count[x - minVal]++;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> idx = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; range; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (count[i]-- &gt; <span class="number">0</span>) arr[idx++] = i + minVal;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">4</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">8</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">1</span>&#125;;</span><br><span class="line">    <span class="built_in">countingSort</span>(v);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">    <span class="comment">// Output: 1 2 2 3 3 4 8</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><table><thead><tr><th>情境</th><th>推薦</th></tr></thead><tbody><tr><td>不知道選什麼</td><td><code>std::sort</code> (Quick&#x2F;IntroSort)</td></tr><tr><td>需要穩定排序</td><td>Merge Sort &#x2F; <code>std::stable_sort</code></td></tr><tr><td>數值範圍小的整數</td><td>Counting Sort</td></tr><tr><td>已接近排序</td><td>Insertion Sort</td></tr><tr><td>O(1) 空間且穩定 O(n log n)</td><td>Heap Sort</td></tr></tbody></table><blockquote><p>💡 競程中 99% 的排序直接用 <code>sort()</code> 就夠了。理解底層邏輯的意義在於：<strong>當題目限制特殊時，你才知道挑哪把刀</strong>。</p></blockquote><hr><div class="cyber-sorting-lab">  <div class="console-header">    <div class="dots"><span></span><span></span><span></span></div>    <div class="console-title">SORTING_MATRIX_SIMULATOR_v2.0 // 點擊演算法後按鈕跳轉說明</div>  </div>  <div class="algo-nav-bar">    <button class="algo-nav-btn active" data-algo="bubble" onclick="window.triggerAlgo('bubble')">Bubble O(n²)</button>    <button class="algo-nav-btn" data-algo="selection" onclick="window.triggerAlgo('selection')">Selection O(n²)</button>    <button class="algo-nav-btn" data-algo="insertion" onclick="window.triggerAlgo('insertion')">Insertion O(n²)</button>    <button class="algo-nav-btn accent" data-algo="merge" onclick="window.triggerAlgo('merge')">Merge O(n log n)</button>    <button class="algo-nav-btn accent" data-algo="quick" onclick="window.triggerAlgo('quick')">Quick O(n log n)</button>    <button class="algo-nav-btn accent" data-algo="heap" onclick="window.triggerAlgo('heap')">Heap O(n log n)</button>    <button class="algo-nav-btn green" data-algo="counting" onclick="window.triggerAlgo('counting')">Counting O(n+k)</button>  </div>  <div class="controls">    <button class="cyber-btn" id="btn-generate"><i class="fa-solid fa-rotate"></i> 新資料</button>    <button class="cyber-btn primary" id="btn-run"><i class="fa-solid fa-play"></i> 執行</button>    <button class="cyber-btn danger" id="btn-stop" disabled><i class="fa-solid fa-stop"></i> 終止</button>    <label class="cyber-btn sound-toggle" id="sound-label">      <input type="checkbox" id="sound-check" checked style="display:none">      <i class="fa-solid fa-volume-high"></i> 音效 ON    </label>  </div>  <div class="status-panel" id="status-panel">System Standby... // Choose an Algorithm</div>  <div class="array-container" id="array-container"></div>  <div class="jump-section-btn-wrap">    <a id="jump-section-link" href="#bubble-section" class="cyber-btn primary">📖 跳轉到 Bubble Sort 說明</a>  </div></div><style>.cyber-sorting-lab {  background: rgba(8, 12, 24, 0.9); border: 1px solid rgba(0, 212, 255, 0.3); border-radius: 8px; margin: 30px 0; overflow: hidden; font-family: 'JetBrains Mono', 'Roboto Mono', monospace; position: relative;}.cyber-sorting-lab::before {  content: ''; position: absolute; inset: 0; background-image: linear-gradient(rgba(0, 212, 255, 0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 212, 255, 0.04) 1px, transparent 1px); background-size: 20px 20px; pointer-events: none; z-index: 0;}.console-header { background: rgba(0, 10, 20, 0.95); padding: 8px 16px; display: flex; align-items: center; border-bottom: 1px solid rgba(0, 212, 255, 0.2); position: relative; z-index: 1; }.console-header .dots span { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 6px; background: #ff5f56; }.console-header .dots span:nth-child(2) { background: #ffbd2e; }.console-header .dots span:nth-child(3) { background: #27c93f; }.console-title { color: #00d4ff; font-size: 0.75rem; margin-left: 14px; letter-spacing: 2px; text-shadow: 0 0 8px #00d4ff; }.algo-nav-bar { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px 20px 0; position: relative; z-index: 1; }.algo-nav-btn { background: rgba(0, 212, 255, 0.05); color: #8fa0b8; border: 1px solid rgba(0, 212, 255, 0.25); padding: 8px 14px; font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; cursor: pointer; border-radius: 4px; transition: all 0.25s; letter-spacing: 1px; }.algo-nav-btn:hover, .algo-nav-btn.active { background: rgba(0, 212, 255, 0.18); color: #fff; border-color: #00d4ff; box-shadow: 0 0 12px rgba(0, 212, 255, 0.4); }.algo-nav-btn.accent { color: #d633ff; border-color: rgba(214,51,255,0.3); }.algo-nav-btn.accent:hover, .algo-nav-btn.accent.active { background: rgba(214,51,255,0.15); border-color: #d633ff; box-shadow: 0 0 12px rgba(214,51,255,0.4); }.algo-nav-btn.green { color: #00e676; border-color: rgba(0,230,118,0.3); }.algo-nav-btn.green:hover, .algo-nav-btn.green.active { background: rgba(0,230,118,0.15); border-color: #00e676; box-shadow: 0 0 12px rgba(0,230,118,0.4); }.controls { display: flex; gap: 10px; flex-wrap: wrap; padding: 12px 20px 0; position: relative; z-index: 1; align-items: center; }.cyber-btn { background: rgba(0, 212, 255, 0.05); color: #8fa0b8; border: 1px solid rgba(0, 212, 255, 0.3); padding: 9px 16px; font-family: 'JetBrains Mono', monospace; font-size: 0.82rem; cursor: pointer; border-radius: 4px; transition: all 0.25s; letter-spacing: 1px; }.cyber-btn:hover:not(:disabled) { background: rgba(0,212,255,0.15); color: #fff; border-color: #00d4ff; box-shadow: 0 0 14px rgba(0,212,255,0.4); }.cyber-btn.primary { color: #00d4ff; border-color: #00d4ff; }.cyber-btn.danger { color: #ff5252; border-color: rgba(255,82,82,0.4); }.cyber-btn.danger:hover:not(:disabled) { background: rgba(255,82,82,0.15); border-color: #ff5252; box-shadow: 0 0 14px rgba(255,82,82,0.5); }.cyber-btn.danger:disabled { opacity: 0.3; cursor: not-allowed; }.cyber-btn.sound-toggle { cursor: pointer; user-select: none; }.cyber-btn.sound-toggle.muted { color: #555; border-color: rgba(100,100,100,0.3); }.status-panel { text-align: center; color: #00e676; font-size: 0.88rem; padding: 10px; text-shadow: 0 0 8px #00e676; animation: pulse-op 2s infinite; position: relative; z-index: 1; }@keyframes pulse-op { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }.array-container { display: flex; align-items: flex-end; height: 220px; gap: 5px; padding: 0 24px 24px; position: relative; z-index: 1; }.array-bar { flex: 1; background: linear-gradient(180deg, #00d4ff 0%, rgba(0,212,255,0.15) 100%); border: 1px solid rgba(0, 212, 255, 0.7); border-bottom: none; border-radius: 3px 3px 0 0; transition: height 0.12s ease, background 0.1s; box-shadow: 0 0 8px rgba(0, 212, 255, 0.25); position: relative; }.array-bar::after { content: attr(data-val); position: absolute; top: -22px; left: 50%; transform: translateX(-50%); color: #00d4ff; font-size: 0.7rem; opacity: 0; transition: opacity 0.2s; }.array-bar:hover::after { opacity: 1; }.array-bar.active { background: linear-gradient(180deg, #ff5252 0%, rgba(255,82,82,0.15) 100%); border-color: #ff5252; box-shadow: 0 0 18px rgba(255,82,82,0.7); }.array-bar.pivot { background: linear-gradient(180deg, #ffbd2e 0%, rgba(255,189,46,0.15) 100%); border-color: #ffbd2e; box-shadow: 0 0 18px rgba(255,189,46,0.7); }.array-bar.sorted { background: linear-gradient(180deg, #00e676 0%, rgba(0,230,118,0.15) 100%); border-color: #00e676; box-shadow: 0 0 12px rgba(0,230,118,0.4); }.jump-section-btn-wrap { text-align: center; padding: 0 20px 20px; position: relative; z-index: 1; }.jump-section-btn-wrap a { display: inline-block; text-decoration: none; }</style><script>(function() {  // 取代 DOMContentLoaded，只要腳本載入立刻執行，避免長條圖等待生成  const container = document.getElementById('array-container');  const statusEl  = document.getElementById('status-panel');  const jumpLink  = document.getElementById('jump-section-link');  const btnRun    = document.getElementById('btn-run');  const btnStop   = document.getElementById('btn-stop');  const btnGen    = document.getElementById('btn-generate');  const soundCheck = document.getElementById('sound-check');  const soundLabel = document.getElementById('sound-label');  let array = [];  const ARRAY_SIZE = 20;  const DELAY = 60;  let isSorting = false;  let currentRunId = 0;  let selectedAlgo = 'bubble';  // -------- Web Audio --------  let audioCtx = null;  function getCtx() {    if (!audioCtx) Object.defineProperty(window, 'AudioContext', { value: window.AudioContext || window.webkitAudioContext });    if (!audioCtx) audioCtx = new AudioContext();    return audioCtx;  }  function beep(freq, type = 'sine', vol = 0.08, dur = 0.06) {    if (!soundCheck || !soundCheck.checked) return;    try {      const ctx = getCtx();      const o = ctx.createOscillator();      const g = ctx.createGain();      o.connect(g); g.connect(ctx.destination);      o.type = type; o.frequency.setValueAtTime(freq, ctx.currentTime);      g.gain.setValueAtTime(vol, ctx.currentTime);      g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + dur);      o.start(ctx.currentTime); o.stop(ctx.currentTime + dur);    } catch(e) {}  }  function playCompare(val) { beep(150 + val * 4, 'sine', 0.06, 0.05); }  function playSwap(val) { beep(300 + val * 4, 'triangle', 0.1, 0.07); }  function playSwapHigh(val) { beep(400 + val * 5, 'sawtooth', 0.07, 0.08); }  function playDone() {    if (!soundCheck || !soundCheck.checked) return;    const notes = [523, 659, 784, 1047];    notes.forEach((f, i) => setTimeout(() => beep(f, 'sine', 0.12, 0.18), i * 80));  }  function playStop() {    beep(130, 'sawtooth', 0.15, 0.15); setTimeout(() => beep(90, 'sawtooth', 0.1, 0.2), 60);  }  if(soundCheck) {    soundCheck.addEventListener('change', () => {      soundLabel.innerHTML = soundCheck.checked ? '<i class="fa-solid fa-volume-high"></i> 音效 ON' : '<i class="fa-solid fa-volume-xmark"></i> 音效 OFF';      soundLabel.classList.toggle('muted', !soundCheck.checked);    });  }  const algoMeta = {    bubble:    { label: 'Bubble Sort',    color: '#00d4ff', section: '#bubble-section',   complexity: 'O(n²)' },    selection: { label: 'Selection Sort', color: '#00d4ff', section: '#selection-section', complexity: 'O(n²)' },    insertion: { label: 'Insertion Sort', color: '#00d4ff', section: '#insertion-section', complexity: 'O(n²)' },    merge:     { label: 'Merge Sort',     color: '#d633ff', section: '#merge-section',     complexity: 'O(n log n)' },    quick:     { label: 'Quick Sort',     color: '#d633ff', section: '#quick-section',     complexity: 'O(n log n)' },    heap:      { label: 'Heap Sort',      color: '#d633ff', section: '#heap-section',      complexity: 'O(n log n)' },    counting:  { label: 'Counting Sort',  color: '#00e676', section: '#counting-section',  complexity: 'O(n+k)' },  };  function delay(ms, runId) {    return new Promise((resolve, reject) => {      const start = performance.now();      function check(now) {        if (currentRunId !== runId) return reject(new Error('STOP'));        if (now - start >= ms) return resolve();        requestAnimationFrame(check);      }      requestAnimationFrame(check);    });  }  function updateStatus(text, color) {    if(!statusEl) return;    statusEl.innerHTML = text; statusEl.style.color = color || '#00e676'; statusEl.style.textShadow = `0 0 8px ${color || '#00e676'}`;  }  function setSortingMode(val) {    isSorting = val;     if(btnRun) btnRun.disabled = val;     if(btnGen) btnGen.disabled = val;     if(btnStop) btnStop.disabled = !val;  }  function cleanBars() {    const bars = getBars(); for (let i = 0; i < bars.length; i++) bars[i].className = 'array-bar';  }  function setBar(bar, val) {    bar.style.height = `${val}%`; bar.setAttribute('data-val', val);  }  function isAscending() {    for (let i = 0; i < array.length - 1; i++) if (array[i] > array[i+1]) return false;    return true;  }  function getBars() { return document.getElementsByClassName('array-bar'); }  // 暴露在 global 讓 HTML buttons call  window.triggerAlgo = function(algo) {    selectedAlgo = algo; // 真正的切換在這！    if (isSorting) { currentRunId++; isSorting = false; setSortingMode(false); playStop(); }    document.querySelectorAll('.algo-nav-btn').forEach(b => b.classList.remove('active'));    let activeBtn = document.querySelector(`[data-algo="${algo}"]`);    if(activeBtn) activeBtn.classList.add('active');    generateArray();    const meta = algoMeta[algo];    if(jumpLink) { jumpLink.href = meta.section; jumpLink.textContent = `📖 跳轉到 ${meta.label} 說明`; }  };  function generateArray() {    array = []; if(!container) return; container.innerHTML = '';    for (let i = 0; i < ARRAY_SIZE; i++) {      const val = Math.floor(Math.random() * 85) + 10;      array.push(val);      const bar = document.createElement('div'); bar.className = 'array-bar';      setBar(bar, val); container.appendChild(bar);    }    updateStatus(`System Ready // ${ARRAY_SIZE} values loaded.`, '#e8edf5');  }  const act = async (bars, i, j, runId) => { bars[i].classList.add('active'); if(j!==undefined) bars[j].classList.add('active'); await delay(DELAY, runId); };  const dAct = (bars, i, j) => { bars[i].classList.remove('active'); if(j!==undefined) bars[j].classList.remove('active'); };  async function bubbleSort(runId) {    const bars = getBars(), meta = algoMeta.bubble;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    for (let i = 0; i < array.length - 1; i++) {      let swapped = false;      for (let j = 0; j < array.length - i - 1; j++) {        await act(bars, j, j+1, runId); playCompare(array[j]);        if (array[j] > array[j+1]) {          [array[j], array[j+1]] = [array[j+1], array[j]];          setBar(bars[j], array[j]); setBar(bars[j+1], array[j+1]); playSwap(array[j]); swapped = true;        }        dAct(bars, j, j+1);      }      bars[array.length - i - 1].classList.add('sorted');      if (!swapped) break;    }    for (let i = 0; i < bars.length; i++) bars[i].classList.add('sorted');  }  async function selectionSort(runId) {    const bars = getBars(), meta = algoMeta.selection;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    for (let i = 0; i < array.length - 1; i++) {      let minIdx = i; bars[minIdx].classList.add('pivot');      for (let j = i + 1; j < array.length; j++) {        await act(bars, j, undefined, runId); playCompare(array[j]);        if (array[j] < array[minIdx]) {          bars[minIdx].classList.remove('pivot'); minIdx = j; bars[minIdx].classList.add('pivot');        }        dAct(bars, j);      }      if (minIdx !== i) {        [array[i], array[minIdx]] = [array[minIdx], array[i]];        setBar(bars[i], array[i]); setBar(bars[minIdx], array[minIdx]); playSwap(array[i]);      }      bars[minIdx].classList.remove('pivot'); bars[i].classList.add('sorted');    }    bars[array.length - 1].classList.add('sorted');  }  async function insertionSort(runId) {    const bars = getBars(), meta = algoMeta.insertion;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    bars[0].classList.add('sorted');    for (let i = 1; i < array.length; i++) {      let key = array[i], j = i - 1;      await act(bars, i, undefined, runId); playCompare(key);      while (j >= 0 && array[j] > key) {        bars[j].classList.add('active'); array[j + 1] = array[j];        setBar(bars[j+1], array[j+1]); playSwap(array[j+1]);        await delay(DELAY, runId); dAct(bars, j); j--;      }      array[j + 1] = key; setBar(bars[j+1], key); dAct(bars, i);      for (let k = 0; k <= i; k++) bars[k].classList.add('sorted');    }  }  async function mergeSortHelper(l, r, bars, runId) {    if (l >= r) return;    const m = l + Math.floor((r - l) / 2);    await mergeSortHelper(l, m, bars, runId); await mergeSortHelper(m + 1, r, bars, runId);    const L = array.slice(l, m + 1), R = array.slice(m + 1, r + 1);    let i = 0, j = 0, k = l;    while (i < L.length && j < R.length) {      bars[k].className = 'array-bar active'; playCompare(Math.max(L[i], R[j])); await delay(DELAY, runId);      if (L[i] <= R[j]) { array[k] = L[i++]; } else { array[k] = R[j++]; }      setBar(bars[k], array[k]); bars[k].className = 'array-bar sorted'; k++;    }    while (i < L.length) {      bars[k].className = 'array-bar active'; await delay(DELAY/2, runId);      array[k] = L[i++]; setBar(bars[k], array[k]); bars[k].className = 'array-bar sorted'; k++;    }    while (j < R.length) {      bars[k].className = 'array-bar active'; await delay(DELAY/2, runId);      array[k] = R[j++]; setBar(bars[k], array[k]); bars[k].className = 'array-bar sorted'; k++;    }  }  async function mergeSort(runId) {    const bars = getBars(), meta = algoMeta.merge;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    await mergeSortHelper(0, array.length - 1, bars, runId);    for (let i = 0; i < bars.length; i++) bars[i].classList.add('sorted');  }  async function qPartition(low, high, bars, runId) {    const pivot = array[high]; bars[high].classList.add('pivot');    let i = low - 1;    for (let j = low; j < high; j++) {      await act(bars, j, undefined, runId); playCompare(array[j]);      if (array[j] < pivot) {        i++; [array[i], array[j]] = [array[j], array[i]];        setBar(bars[i], array[i]); setBar(bars[j], array[j]); playSwapHigh(array[i]);      }      dAct(bars, j);    }    [array[i+1], array[high]] = [array[high], array[i+1]];    setBar(bars[i+1], array[i+1]); setBar(bars[high], array[high]);    bars[high].classList.remove('pivot'); bars[i+1].classList.add('sorted');    return i + 1;  }  async function quickSortHelper(low, high, bars, runId) {    if (low < high) {      const pi = await qPartition(low, high, bars, runId);      await quickSortHelper(low, pi - 1, bars, runId); await quickSortHelper(pi + 1, high, bars, runId);    } else if (low === high) bars[low].classList.add('sorted');  }  async function quickSort(runId) {    const bars = getBars(), meta = algoMeta.quick;    updateStatus(`>> Executing [ ${meta.label} ] avg ${meta.complexity}`, meta.color);    await quickSortHelper(0, array.length - 1, bars, runId);    for (let i = 0; i < bars.length; i++) bars[i].classList.add('sorted');  }  async function heapify(n, i, bars, runId) {    let largest = i, l = 2*i+1, r = 2*i+2;    if (l < n && array[l] > array[largest]) largest = l;    if (r < n && array[r] > array[largest]) largest = r;    if (largest !== i) {      await act(bars, i, largest, runId); playCompare(array[largest]);      [array[i], array[largest]] = [array[largest], array[i]];      setBar(bars[i], array[i]); setBar(bars[largest], array[largest]); playSwap(array[i]);      dAct(bars, i, largest); await heapify(n, largest, bars, runId);    }  }  async function heapSort(runId) {    const bars = getBars(), meta = algoMeta.heap, n = array.length;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    for (let i = Math.floor(n/2)-1; i >= 0; i--) await heapify(n, i, bars, runId);    for (let i = n-1; i > 0; i--) {      bars[0].classList.add('pivot'); await delay(DELAY, runId);      [array[0], array[i]] = [array[i], array[0]];      setBar(bars[0], array[0]); setBar(bars[i], array[i]); playSwapHigh(array[i]);      bars[0].classList.remove('pivot'); bars[i].classList.add('sorted');      await heapify(i, 0, bars, runId);    }    bars[0].classList.add('sorted');  }  async function countingSort(runId) {    const bars = getBars(), meta = algoMeta.counting;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity} // No comparisons!`, meta.color);    const minVal = Math.min(...array), maxVal = Math.max(...array);    const count = new Array(maxVal - minVal + 1).fill(0);    for (let x of array) count[x - minVal]++;    for (let i = 0; i < bars.length; i++) bars[i].classList.add('active');    beep(300, 'square', 0.08, 0.3); await delay(400, runId);    for (let i = 0; i < bars.length; i++) bars[i].classList.remove('active');    let idx = 0;    for (let i = 0; i < count.length; i++) {      while (count[i]-- > 0) {        array[idx] = i + minVal; setBar(bars[idx], array[idx]); bars[idx].classList.add('sorted');        playCompare(array[idx]); await delay(DELAY, runId); idx++;      }    }  }  // EVENT LISTENERS  const algoFns = { bubble: bubbleSort, selection: selectionSort, insertion: insertionSort, merge: mergeSort, quick: quickSort, heap: heapSort, counting: countingSort };  if(btnRun) {    btnRun.addEventListener('click', async () => {      if (isSorting) return;      if (isAscending()) generateArray();      cleanBars(); currentRunId++; const runId = currentRunId; setSortingMode(true);      try {        if (!algoFns[selectedAlgo]) throw new Error('Unknown Algo');        await algoFns[selectedAlgo](runId);        if (runId === currentRunId) {          updateStatus(`>> [ ${algoMeta[selectedAlgo].label} ] Done! ✅ Scroll down to read the explanation.`, '#00e676');          playDone();        }      } catch(e) {        if (e.message === 'STOP') {          cleanBars(); updateStatus(`>> [ ABORTED ] // 排序已終止，可重新生成資料或切換算法`, '#ff5252');        }      } finally {        if (runId === currentRunId) setSortingMode(false);      }    });  }  if(btnStop) {    btnStop.addEventListener('click', () => {      if (!isSorting) return;      currentRunId++; isSorting = false; setSortingMode(false); playStop();    });  }  if(btnGen) btnGen.addEventListener('click', () => { if (!isSorting) generateArray(); });  window.triggerAlgo('bubble');})();</script><hr><h2 id="🔴-Bubble-Sort（氣泡排序）-1"><a href="#🔴-Bubble-Sort（氣泡排序）-1" class="headerlink" title="🔴 Bubble Sort（氣泡排序）"></a><span id="bubble-section">🔴 Bubble Sort（氣泡排序）</span></h2><p><strong>核心思想</strong>：每輪把相鄰的兩個元素互比，如果左邊比較大就交換。這樣最大數字會像泡泡一樣「浮」到最右端，然後一直縮小範圍重複這個動作。</p><p><strong>什麼時候用？</strong> 幾乎不用於實戰，但因為邏輯最直覺，是學習演算法的起點。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bubbleSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="type">bool</span> swapped = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n - i - <span class="number">1</span>; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &gt; arr[j + <span class="number">1</span>]) &#123;</span><br><span class="line">                <span class="built_in">swap</span>(arr[j], arr[j + <span class="number">1</span>]);</span><br><span class="line">                swapped = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!swapped) <span class="keyword">break</span>; <span class="comment">// 優化：提早結束</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">64</span>, <span class="number">34</span>, <span class="number">25</span>, <span class="number">12</span>, <span class="number">22</span>, <span class="number">11</span>, <span class="number">90</span>&#125;;</span><br><span class="line">    <span class="built_in">bubbleSort</span>(v);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Output: 11 12 22 25 34 64 90</span></span><br></pre></td></tr></table></figure><hr><h2 id="🔴-Selection-Sort（選擇排序）-1"><a href="#🔴-Selection-Sort（選擇排序）-1" class="headerlink" title="🔴 Selection Sort（選擇排序）"></a><span id="selection-section">🔴 Selection Sort（選擇排序）</span></h2><p><strong>核心思想</strong>：每一輪從「尚未排序的部分」找出<strong>最小值</strong>，然後跟最前面的元素交換。每輪確定一個位置正確的元素。</p><p><strong>注意</strong>：Selection Sort 永遠執行 O(n²) 次比較，<strong>無論資料本身是否已排序</strong>，這是它的最大缺點。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">selectionSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="type">int</span> minIdx = i;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = i + <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &lt; arr[minIdx]) minIdx = j;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">swap</span>(arr[i], arr[minIdx]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🔴-Insertion-Sort（插入排序）-1"><a href="#🔴-Insertion-Sort（插入排序）-1" class="headerlink" title="🔴 Insertion Sort（插入排序）"></a><span id="insertion-section">🔴 Insertion Sort（插入排序）</span></h2><p><strong>核心思想</strong>：想像左手拿著已排好的牌，右手每次從右邊抽一張，然後插進左手牌堆的正確位置。</p><p><strong>什麼時候用？</strong> 資料已接近排序完成時（nearly sorted），時間複雜度接近 O(n)！在 <code>std::sort</code> 的 IntroSort 實作中，當子陣列縮到小於 16 個元素時，會切換為 Insertion Sort。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">insertionSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> key = arr[i];</span><br><span class="line">        <span class="type">int</span> j = i - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (j &gt;= <span class="number">0</span> &amp;&amp; arr[j] &gt; key) &#123;</span><br><span class="line">            arr[j + <span class="number">1</span>] = arr[j];</span><br><span class="line">            j--;</span><br><span class="line">        &#125;</span><br><span class="line">        arr[j + <span class="number">1</span>] = key;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🟡-Merge-Sort（合併排序）-1"><a href="#🟡-Merge-Sort（合併排序）-1" class="headerlink" title="🟡 Merge Sort（合併排序）"></a><span id="merge-section">🟡 Merge Sort（合併排序）</span></h2><p><strong>核心思想</strong>：<strong>分治法 (Divide and Conquer)</strong>. 不斷把陣列對半切，直到每個子陣列只剩 1 個元素。然後再一一將兩個有序陣列「合併」成一個有序陣列。</p><p><strong>什麼時候用？</strong> 需要<strong>穩定排序</strong>且時間複雜度穩定在 O(n log n) 的場合，例如處理鏈結串列、外部排序（待排資料超過記憶體大小）。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">merge</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> l, <span class="type">int</span> m, <span class="type">int</span> r)</span> </span>&#123;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">L</span><span class="params">(arr.begin() + l, arr.begin() + m + <span class="number">1</span>)</span></span>;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">R</span><span class="params">(arr.begin() + m + <span class="number">1</span>, arr.begin() + r + <span class="number">1</span>)</span></span>;</span><br><span class="line">    <span class="type">int</span> i = <span class="number">0</span>, j = <span class="number">0</span>, k = l;</span><br><span class="line">    <span class="keyword">while</span> (i &lt; L.<span class="built_in">size</span>() &amp;&amp; j &lt; R.<span class="built_in">size</span>()) &#123;</span><br><span class="line">        <span class="keyword">if</span> (L[i] &lt;= R[j]) arr[k++] = L[i++];</span><br><span class="line">        <span class="keyword">else</span> arr[k++] = R[j++];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (i &lt; L.<span class="built_in">size</span>()) arr[k++] = L[i++];</span><br><span class="line">    <span class="keyword">while</span> (j &lt; R.<span class="built_in">size</span>()) arr[k++] = R[j++];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">mergeSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> l, <span class="type">int</span> r)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (l &gt;= r) <span class="keyword">return</span>;</span><br><span class="line">    <span class="type">int</span> m = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">    <span class="built_in">mergeSort</span>(arr, l, m);</span><br><span class="line">    <span class="built_in">mergeSort</span>(arr, m + <span class="number">1</span>, r);</span><br><span class="line">    <span class="built_in">merge</span>(arr, l, m, r);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">38</span>, <span class="number">27</span>, <span class="number">43</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">82</span>, <span class="number">10</span>&#125;;</span><br><span class="line">    <span class="built_in">mergeSort</span>(v, <span class="number">0</span>, v.<span class="built_in">size</span>() - <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🟡-Quick-Sort（快速排序）-1"><a href="#🟡-Quick-Sort（快速排序）-1" class="headerlink" title="🟡 Quick Sort（快速排序）"></a><span id="quick-section">🟡 Quick Sort（快速排序）</span></h2><p><strong>核心思想</strong>：挑一個 <strong>Pivot（基準點）</strong>，把所有比 Pivot 小的元素放到左邊，比 Pivot 大的放到右邊。然後遞迴對左右兩側重複這個動作。</p><p><strong>什麼時候用？</strong> 這是實際最常用的排序法。<code>std::sort</code> 的底層 (IntroSort) 即是以 Quick Sort 為核心。平均 O(n log n)，且記憶體使用效率高（In-place）。</p><blockquote><p><strong>⚠️ 坑點</strong>：如果每次都選最小&#x2F;最大值當 Pivot，退化成 O(n²)。實務上要用<strong>亂數選 Pivot</strong> 或<strong>三數取中</strong>來避免。</p></blockquote><p><strong>C++ 競程寫法（推薦用 <code>std::sort</code>）：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 競程直接用 std::sort，底層已是 IntroSort (Quick+Heap+Insertion)</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">10</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">1</span>, <span class="number">5</span>&#125;;</span><br><span class="line">    <span class="built_in">sort</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>()); <span class="comment">// 升冪</span></span><br><span class="line">    <span class="built_in">sort</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), <span class="built_in">greater</span>&lt;<span class="type">int</span>&gt;()); <span class="comment">// 降冪</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 自訂排序</span></span><br><span class="line">    <span class="built_in">sort</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), [](<span class="type">int</span> a, <span class="type">int</span> b) &#123;</span><br><span class="line">        <span class="keyword">return</span> a % <span class="number">3</span> &lt; b % <span class="number">3</span>; <span class="comment">// 按模 3 餘數升冪</span></span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 手刻版本（理解用）</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">partition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> pivot = arr[high];</span><br><span class="line">    <span class="type">int</span> i = low - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = low; j &lt; high; j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arr[j] &lt; pivot) <span class="built_in">swap</span>(arr[++i], arr[j]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">swap</span>(arr[i + <span class="number">1</span>], arr[high]);</span><br><span class="line">    <span class="keyword">return</span> i + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">quickSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (low &lt; high) &#123;</span><br><span class="line">        <span class="type">int</span> pi = <span class="built_in">partition</span>(arr, low, high);</span><br><span class="line">        <span class="built_in">quickSort</span>(arr, low, pi - <span class="number">1</span>);</span><br><span class="line">        <span class="built_in">quickSort</span>(arr, pi + <span class="number">1</span>, high);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🟡-Heap-Sort（堆積排序）-1"><a href="#🟡-Heap-Sort（堆積排序）-1" class="headerlink" title="🟡 Heap Sort（堆積排序）"></a><span id="heap-section">🟡 Heap Sort（堆積排序）</span></h2><p><strong>核心思想</strong>：利用 <strong>Max-Heap（最大堆積）</strong> 這種資料結構。先把陣列建成 Max-Heap，然後每次把堆頂（最大值）取出放到末端，再對剩下的部分重新整理成 Heap，重複直到結束。</p><p><strong>什麼時候用？</strong> 當你需要 O(n log n) <strong>且</strong> O(1) 空間複雜度的穩定效能（Quick Sort 最差退化，Merge Sort 需要額外空間）。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 競程中 priority_queue 就是一個 Max-Heap</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    priority_queue&lt;<span class="type">int</span>&gt; maxHeap;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">3</span>, <span class="number">1</span>, <span class="number">4</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">9</span>, <span class="number">2</span>, <span class="number">6</span>&#125;;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) maxHeap.<span class="built_in">push</span>(x);</span><br><span class="line"></span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; sorted;</span><br><span class="line">    <span class="keyword">while</span> (!maxHeap.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        sorted.<span class="built_in">push_back</span>(maxHeap.<span class="built_in">top</span>());</span><br><span class="line">        maxHeap.<span class="built_in">pop</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// sorted 是降冪，反轉後得升冪</span></span><br><span class="line">    <span class="built_in">reverse</span>(sorted.<span class="built_in">begin</span>(), sorted.<span class="built_in">end</span>());</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : sorted) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 手刻 Heap Sort</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">heapify</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> n, <span class="type">int</span> i)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> largest = i, l = <span class="number">2</span>*i<span class="number">+1</span>, r = <span class="number">2</span>*i<span class="number">+2</span>;</span><br><span class="line">    <span class="keyword">if</span> (l &lt; n &amp;&amp; arr[l] &gt; arr[largest]) largest = l;</span><br><span class="line">    <span class="keyword">if</span> (r &lt; n &amp;&amp; arr[r] &gt; arr[largest]) largest = r;</span><br><span class="line">    <span class="keyword">if</span> (largest != i) &#123;</span><br><span class="line">        <span class="built_in">swap</span>(arr[i], arr[largest]);</span><br><span class="line">        <span class="built_in">heapify</span>(arr, n, largest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">heapSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n = arr.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = n/<span class="number">2</span><span class="number">-1</span>; i &gt;= <span class="number">0</span>; i--) <span class="built_in">heapify</span>(arr, n, i);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = n<span class="number">-1</span>; i &gt; <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="built_in">swap</span>(arr[<span class="number">0</span>], arr[i]);</span><br><span class="line">        <span class="built_in">heapify</span>(arr, i, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🔵-Counting-Sort（計數排序）-1"><a href="#🔵-Counting-Sort（計數排序）-1" class="headerlink" title="🔵 Counting Sort（計數排序）"></a><span id="counting-section">🔵 Counting Sort（計數排序）</span></h2><p><strong>核心思想</strong>：不靠「比較」來排序！先統計每個數字出現幾次，再依序填回陣列。</p><p><strong>限制條件</strong>：只適用於整數，且數值範圍 k 不能太大（k &#x3D; max - min），因為需要建立大小為 k 的計數陣列。</p><p><strong>什麼時候用？</strong> 數字範圍明確且較小時（例如成績 0<del>100、字元 0</del>255、APCS 中 N ≤ 10⁶ 且值域為 0~10⁶）可達到 O(n+k) 的速度，<strong>比比較式排序更快</strong>。</p><p><strong>C++ 競程寫法：</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">countingSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (arr.<span class="built_in">empty</span>()) <span class="keyword">return</span>;</span><br><span class="line">    <span class="type">int</span> maxVal = *<span class="built_in">max_element</span>(arr.<span class="built_in">begin</span>(), arr.<span class="built_in">end</span>());</span><br><span class="line">    <span class="type">int</span> minVal = *<span class="built_in">min_element</span>(arr.<span class="built_in">begin</span>(), arr.<span class="built_in">end</span>());</span><br><span class="line">    <span class="type">int</span> range = maxVal - minVal + <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">count</span><span class="params">(range, <span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : arr) count[x - minVal]++;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> idx = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; range; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (count[i]-- &gt; <span class="number">0</span>) arr[idx++] = i + minVal;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; v = &#123;<span class="number">4</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">8</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">1</span>&#125;;</span><br><span class="line">    <span class="built_in">countingSort</span>(v);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> x : v) cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">    <span class="comment">// Output: 1 2 2 3 3 4 8</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="結論-1"><a href="#結論-1" class="headerlink" title="結論"></a>結論</h2><table><thead><tr><th>情境</th><th>推薦</th></tr></thead><tbody><tr><td>不知道選什麼</td><td><code>std::sort</code> (Quick&#x2F;IntroSort)</td></tr><tr><td>需要穩定排序</td><td>Merge Sort &#x2F; <code>std::stable_sort</code></td></tr><tr><td>數值範圍小的整數</td><td>Counting Sort</td></tr><tr><td>已接近排序</td><td>Insertion Sort</td></tr><tr><td>O(1) 空間且穩定 O(n log n)</td><td>Heap Sort</td></tr></tbody></table><blockquote><p>💡 競程中 99% 的排序直接用 <code>sort()</code> 就夠了。理解底層邏輯的意義在於：<strong>當題目限制特殊時，你才知道挑哪把刀</strong>。</p></blockquote><hr><!-- Sorting Visualizer Script + Styles --><style>.cyber-sorting-lab {  background: rgba(8, 12, 24, 0.9);  border: 1px solid rgba(0, 212, 255, 0.3);  border-radius: 8px;  margin: 30px 0;  overflow: hidden;  font-family: 'JetBrains Mono', 'Roboto Mono', monospace;  position: relative;}.cyber-sorting-lab::before {  content: '';  position: absolute;  inset: 0;  background-image: linear-gradient(rgba(0, 212, 255, 0.04) 1px, transparent 1px),    linear-gradient(90deg, rgba(0, 212, 255, 0.04) 1px, transparent 1px);  background-size: 20px 20px;  pointer-events: none;  z-index: 0;}.console-header {  background: rgba(0, 10, 20, 0.95);  padding: 8px 16px;  display: flex;  align-items: center;  border-bottom: 1px solid rgba(0, 212, 255, 0.2);  position: relative; z-index: 1;}.console-header .dots span {  display: inline-block; width: 12px; height: 12px;  border-radius: 50%; margin-right: 6px; background: #ff5f56;}.console-header .dots span:nth-child(2) { background: #ffbd2e; }.console-header .dots span:nth-child(3) { background: #27c93f; }.console-title { color: #00d4ff; font-size: 0.75rem; margin-left: 14px; letter-spacing: 2px; text-shadow: 0 0 8px #00d4ff; }.algo-nav-bar {  display: flex; flex-wrap: wrap; gap: 8px;  padding: 14px 20px 0;  position: relative; z-index: 1;}.algo-nav-btn {  background: rgba(0, 212, 255, 0.05);  color: #8fa0b8;  border: 1px solid rgba(0, 212, 255, 0.25);  padding: 8px 14px;  font-family: 'JetBrains Mono', monospace; font-size: 0.8rem;  cursor: pointer; border-radius: 4px;  transition: all 0.25s; letter-spacing: 1px;}.algo-nav-btn:hover, .algo-nav-btn.active {  background: rgba(0, 212, 255, 0.18);  color: #fff; border-color: #00d4ff;  box-shadow: 0 0 12px rgba(0, 212, 255, 0.4);}.algo-nav-btn.accent { color: #d633ff; border-color: rgba(214,51,255,0.3); }.algo-nav-btn.accent:hover, .algo-nav-btn.accent.active {  background: rgba(214,51,255,0.15);  border-color: #d633ff;  box-shadow: 0 0 12px rgba(214,51,255,0.4);}.algo-nav-btn.green { color: #00e676; border-color: rgba(0,230,118,0.3); }.algo-nav-btn.green:hover, .algo-nav-btn.green.active {  background: rgba(0,230,118,0.15);  border-color: #00e676;  box-shadow: 0 0 12px rgba(0,230,118,0.4);}.controls {  display: flex; gap: 12px; flex-wrap: wrap;  padding: 12px 20px 0;  position: relative; z-index: 1;}.cyber-btn {  background: rgba(0, 212, 255, 0.05); color: #8fa0b8;  border: 1px solid rgba(0, 212, 255, 0.3);  padding: 9px 16px;  font-family: 'JetBrains Mono', monospace; font-size: 0.82rem;  cursor: pointer; border-radius: 4px;  transition: all 0.25s; letter-spacing: 1px;}.cyber-btn:hover { background: rgba(0,212,255,0.15); color: #fff; border-color: #00d4ff; box-shadow: 0 0 14px rgba(0,212,255,0.4); }.cyber-btn.primary { color: #00d4ff; border-color: #00d4ff; }.status-panel {  text-align: center; color: #00e676; font-size: 0.88rem;  padding: 10px; text-shadow: 0 0 8px #00e676;  animation: pulse-op 2s infinite; position: relative; z-index: 1;}@keyframes pulse-op { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }.array-container {  display: flex; align-items: flex-end;  height: 220px; gap: 5px;  padding: 0 24px 24px;  position: relative; z-index: 1;}.array-bar {  flex: 1;  background: linear-gradient(180deg, #00d4ff 0%, rgba(0,212,255,0.15) 100%);  border: 1px solid rgba(0, 212, 255, 0.7); border-bottom: none;  border-radius: 3px 3px 0 0;  transition: height 0.12s ease, background 0.1s;  box-shadow: 0 0 8px rgba(0, 212, 255, 0.25);  position: relative;}.array-bar::after {  content: attr(data-val);  position: absolute; top: -22px; left: 50%; transform: translateX(-50%);  color: #00d4ff; font-size: 0.7rem; opacity: 0; transition: opacity 0.2s;}.array-bar:hover::after { opacity: 1; }.array-bar.active {  background: linear-gradient(180deg, #ff5252 0%, rgba(255,82,82,0.15) 100%);  border-color: #ff5252; box-shadow: 0 0 18px rgba(255,82,82,0.7);  transform: scaleY(1.015);}.array-bar.pivot {  background: linear-gradient(180deg, #ffbd2e 0%, rgba(255,189,46,0.15) 100%);  border-color: #ffbd2e; box-shadow: 0 0 18px rgba(255,189,46,0.7);}.array-bar.sorted {  background: linear-gradient(180deg, #00e676 0%, rgba(0,230,118,0.15) 100%);  border-color: #00e676; box-shadow: 0 0 12px rgba(0,230,118,0.4);}.jump-section-btn-wrap {  text-align: center; padding: 0 20px 20px;  position: relative; z-index: 1;}.jump-section-btn-wrap a { display: inline-block; text-decoration: none; }</style><script>document.addEventListener('DOMContentLoaded', () => {  const container = document.getElementById('array-container');  const statusEl = document.getElementById('status-panel');  const jumpLink = document.getElementById('jump-section-link');  let array = [];  const ARRAY_SIZE = 22;  const DELAY = 70;  let isSorting = false;  let selectedAlgo = 'bubble';  const algoMeta = {    bubble:    { label: 'Bubble Sort',    color: 'var(--tech-cyan)',    section: '#bubble-section',   complexity: 'O(n²)' },    selection: { label: 'Selection Sort', color: 'var(--tech-cyan)',    section: '#selection-section', complexity: 'O(n²)' },    insertion: { label: 'Insertion Sort', color: 'var(--tech-cyan)',    section: '#insertion-section', complexity: 'O(n²)' },    merge:     { label: 'Merge Sort',     color: '#d633ff',             section: '#merge-section',     complexity: 'O(n log n)' },    quick:     { label: 'Quick Sort',     color: '#d633ff',             section: '#quick-section',     complexity: 'O(n log n)' },    heap:      { label: 'Heap Sort',      color: '#d633ff',             section: '#heap-section',      complexity: 'O(n log n)' },    counting:  { label: 'Counting Sort',  color: '#00e676',             section: '#counting-section',  complexity: 'O(n+k)' },  };  const sleep = ms => new Promise(r => setTimeout(r, ms));  function updateStatus(text, color) {    statusEl.innerHTML = text;    statusEl.style.color = color || '#00e676';    statusEl.style.textShadow = `0 0 8px ${color || '#00e676'}`;  }  window.selectAlgo = function(algo) {    selectedAlgo = algo;    document.querySelectorAll('.algo-nav-btn').forEach(b => b.classList.remove('active'));    document.querySelector(`[data-algo="${algo}"]`).classList.add('active');    const meta = algoMeta[algo];    updateStatus(`>> Selected: [ ${meta.label} ] - ${meta.complexity}`, meta.color);    jumpLink.href = meta.section;    jumpLink.textContent = `📖 跳轉到 ${meta.label} 說明`;  };  function generateArray() {    if (isSorting) return;    array = [];    container.innerHTML = '';    for (let i = 0; i < ARRAY_SIZE; i++) {      // For counting sort, keep values small      const val = Math.floor(Math.random() * 85) + 10;      array.push(val);      const bar = document.createElement('div');      bar.classList.add('array-bar');      bar.style.height = `${val}%`;      bar.setAttribute('data-val', val);      container.appendChild(bar);    }    const meta = algoMeta[selectedAlgo];    updateStatus(`System Ready // ${ARRAY_SIZE} values loaded. Run ${meta.label}?`, '#e8edf5');  }  function getBars() { return document.getElementsByClassName('array-bar'); }  // ---------- BUBBLE ----------  async function bubbleSort() {    const bars = getBars();    const meta = algoMeta.bubble;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    for (let i = 0; i < array.length - 1; i++) {      let swapped = false;      for (let j = 0; j < array.length - i - 1; j++) {        bars[j].classList.add('active'); bars[j+1].classList.add('active');        await sleep(DELAY);        if (array[j] > array[j+1]) {          [array[j], array[j+1]] = [array[j+1], array[j]];          bars[j].style.height = `${array[j]}%`; bars[j].setAttribute('data-val', array[j]);          bars[j+1].style.height = `${array[j+1]}%`; bars[j+1].setAttribute('data-val', array[j+1]);          swapped = true;        }        bars[j].classList.remove('active'); bars[j+1].classList.remove('active');      }      bars[array.length - i - 1].classList.add('sorted');      if (!swapped) break;    }    for (let i = 0; i < bars.length; i++) bars[i].classList.add('sorted');  }  // ---------- SELECTION ----------  async function selectionSort() {    const bars = getBars();    const meta = algoMeta.selection;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    for (let i = 0; i < array.length - 1; i++) {      let minIdx = i;      bars[minIdx].classList.add('pivot');      for (let j = i + 1; j < array.length; j++) {        bars[j].classList.add('active');        await sleep(DELAY);        if (array[j] < array[minIdx]) {          bars[minIdx].classList.remove('pivot');          minIdx = j;          bars[minIdx].classList.add('pivot');        }        bars[j].classList.remove('active');      }      if (minIdx !== i) {        [array[i], array[minIdx]] = [array[minIdx], array[i]];        bars[i].style.height = `${array[i]}%`; bars[i].setAttribute('data-val', array[i]);        bars[minIdx].style.height = `${array[minIdx]}%`; bars[minIdx].setAttribute('data-val', array[minIdx]);      }      bars[minIdx].classList.remove('pivot');      bars[i].classList.add('sorted');    }    bars[array.length - 1].classList.add('sorted');  }  // ---------- INSERTION ----------  async function insertionSort() {    const bars = getBars();    const meta = algoMeta.insertion;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    bars[0].classList.add('sorted');    for (let i = 1; i < array.length; i++) {      let key = array[i], j = i - 1;      bars[i].classList.add('active');      await sleep(DELAY);      while (j >= 0 && array[j] > key) {        bars[j].classList.add('active');        array[j + 1] = array[j];        bars[j+1].style.height = `${array[j+1]}%`; bars[j+1].setAttribute('data-val', array[j+1]);        await sleep(DELAY);        bars[j].classList.remove('active');        j--;      }      array[j + 1] = key;      bars[j+1].style.height = `${key}%`; bars[j+1].setAttribute('data-val', key);      bars[i].classList.remove('active');      for (let k = 0; k <= i; k++) bars[k].classList.add('sorted');    }  }  // ---------- MERGE ----------  async function mergeSortHelper(l, r, bars) {    if (l >= r) return;    const m = l + Math.floor((r - l) / 2);    await mergeSortHelper(l, m, bars);    await mergeSortHelper(m + 1, r, bars);    // Merge    const L = array.slice(l, m + 1), R = array.slice(m + 1, r + 1);    let i = 0, j = 0, k = l;    while (i < L.length && j < R.length) {      bars[k].classList.add('active');      await sleep(DELAY);      if (L[i] <= R[j]) { array[k] = L[i++]; }      else { array[k] = R[j++]; }      bars[k].style.height = `${array[k]}%`; bars[k].setAttribute('data-val', array[k]);      bars[k].classList.remove('active');      bars[k].classList.add('sorted');      k++;    }    while (i < L.length) {      bars[k].classList.add('active'); await sleep(DELAY/2);      array[k] = L[i++];      bars[k].style.height = `${array[k]}%`; bars[k].setAttribute('data-val', array[k]);      bars[k].classList.remove('active'); bars[k].classList.add('sorted'); k++;    }    while (j < R.length) {      bars[k].classList.add('active'); await sleep(DELAY/2);      array[k] = R[j++];      bars[k].style.height = `${array[k]}%`; bars[k].setAttribute('data-val', array[k]);      bars[k].classList.remove('active'); bars[k].classList.add('sorted'); k++;    }  }  async function mergeSort() {    const bars = getBars();    const meta = algoMeta.merge;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    await mergeSortHelper(0, array.length - 1, bars);    for (let i = 0; i < bars.length; i++) bars[i].classList.add('sorted');  }  // ---------- QUICK ----------  async function partition(low, high, bars) {    const pivot = array[high];    bars[high].classList.add('pivot');    let i = low - 1;    for (let j = low; j < high; j++) {      bars[j].classList.add('active');      await sleep(DELAY);      if (array[j] < pivot) {        i++;        [array[i], array[j]] = [array[j], array[i]];        bars[i].style.height = `${array[i]}%`; bars[i].setAttribute('data-val', array[i]);        bars[j].style.height = `${array[j]}%`; bars[j].setAttribute('data-val', array[j]);      }      bars[j].classList.remove('active');    }    [array[i+1], array[high]] = [array[high], array[i+1]];    bars[i+1].style.height = `${array[i+1]}%`; bars[i+1].setAttribute('data-val', array[i+1]);    bars[high].style.height = `${array[high]}%`; bars[high].setAttribute('data-val', array[high]);    bars[high].classList.remove('pivot');    bars[i+1].classList.add('sorted');    return i + 1;  }  async function quickSortHelper(low, high, bars) {    if (low < high) {      const pi = await partition(low, high, bars);      await quickSortHelper(low, pi - 1, bars);      await quickSortHelper(pi + 1, high, bars);    } else if (low === high) bars[low].classList.add('sorted');  }  async function quickSort() {    const bars = getBars();    const meta = algoMeta.quick;    updateStatus(`>> Executing [ ${meta.label} ] avg ${meta.complexity}`, meta.color);    await quickSortHelper(0, array.length - 1, bars);    for (let i = 0; i < bars.length; i++) bars[i].classList.add('sorted');  }  // ---------- HEAP ----------  async function heapify(n, i, bars) {    let largest = i, l = 2*i+1, r = 2*i+2;    if (l < n && array[l] > array[largest]) largest = l;    if (r < n && array[r] > array[largest]) largest = r;    if (largest !== i) {      bars[i].classList.add('active'); bars[largest].classList.add('active');      await sleep(DELAY);      [array[i], array[largest]] = [array[largest], array[i]];      bars[i].style.height = `${array[i]}%`; bars[i].setAttribute('data-val', array[i]);      bars[largest].style.height = `${array[largest]}%`; bars[largest].setAttribute('data-val', array[largest]);      bars[i].classList.remove('active'); bars[largest].classList.remove('active');      await heapify(n, largest, bars);    }  }  async function heapSort() {    const bars = getBars();    const meta = algoMeta.heap;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity}`, meta.color);    const n = array.length;    for (let i = Math.floor(n/2)-1; i >= 0; i--) await heapify(n, i, bars);    for (let i = n-1; i > 0; i--) {      bars[0].classList.add('pivot');      await sleep(DELAY);      [array[0], array[i]] = [array[i], array[0]];      bars[0].style.height = `${array[0]}%`; bars[0].setAttribute('data-val', array[0]);      bars[i].style.height = `${array[i]}%`; bars[i].setAttribute('data-val', array[i]);      bars[0].classList.remove('pivot'); bars[i].classList.add('sorted');      await heapify(i, 0, bars);    }    bars[0].classList.add('sorted');  }  // ---------- COUNTING ----------  async function countingSort() {    const bars = getBars();    const meta = algoMeta.counting;    updateStatus(`>> Executing [ ${meta.label} ] ${meta.complexity} // No comparisons!`, meta.color);    const minVal = Math.min(...array), maxVal = Math.max(...array);    const count = new Array(maxVal - minVal + 1).fill(0);    for (let x of array) count[x - minVal]++;    // Flash all bars red to show "counting" phase    for (let i = 0; i < bars.length; i++) bars[i].classList.add('active');    await sleep(400);    for (let i = 0; i < bars.length; i++) bars[i].classList.remove('active');    let idx = 0;    for (let i = 0; i < count.length; i++) {      while (count[i]-- > 0) {        array[idx] = i + minVal;        bars[idx].style.height = `${array[idx]}%`; bars[idx].setAttribute('data-val', array[idx]);        bars[idx].classList.add('sorted');        await sleep(DELAY);        idx++;      }    }  }  // ---------- MAIN RUN ----------  const algoFns = { bubble: bubbleSort, selection: selectionSort, insertion: insertionSort, merge: mergeSort, quick: quickSort, heap: heapSort, counting: countingSort };  document.getElementById('btn-run').addEventListener('click', async () => {    if (isSorting) return;    isSorting = true;    await algoFns[selectedAlgo]();    const meta = algoMeta[selectedAlgo];    updateStatus(`>> [ ${meta.label} ] Done! ✅ Scroll down to read the explanation.`, '#00e676');    isSorting = false;  });  document.getElementById('btn-generate').addEventListener('click', () => {    if (isSorting) return;    generateArray();  });  selectAlgo('bubble');  generateArray();});</script>]]></content>
    
    
    <summary type="html">&lt;!-- 頂部互動區：點擊演算法演示並跳轉 --&gt;
&lt;div class=&quot;cyber-sorting-lab&quot;&gt;
  &lt;div class=&quot;console-header&quot;&gt;
    &lt;div class=&quot;dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;console-title&quot;&gt;SORTING_MATRIX_SIMULATOR_v2.0 // 點擊演算法後按鈕跳轉說明&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;algo-nav-bar&quot;&gt;
    &lt;button class=&quot;algo-nav-btn active&quot; data-algo=&quot;bubble&quot; onclick=&quot;selectAlgo(&#39;bubble&#39;)&quot;&gt;Bubble O(n²)&lt;/button&gt;
    &lt;button class=&quot;algo-nav-btn&quot; data-algo=&quot;selection&quot; onclick=&quot;selectAlgo(&#39;selection&#39;)&quot;&gt;Selection O(n²)&lt;/button&gt;
    &lt;button class=&quot;algo-nav-btn&quot; data-algo=&quot;insertion&quot; onclick=&quot;selectAlgo(&#39;insertion&#39;)&quot;&gt;Insertion O(n²)&lt;/button&gt;
    &lt;button class=&quot;algo-nav-btn accent&quot; data-algo=&quot;merge&quot; onclick=&quot;selectAlgo(&#39;merge&#39;)&quot;&gt;Merge O(n log n)&lt;/button&gt;
    &lt;button class=&quot;algo-nav-btn accent&quot; data-algo=&quot;quick&quot; onclick=&quot;selectAlgo(&#39;quick&#39;)&quot;&gt;Quick O(n log n)&lt;/button&gt;
    &lt;button class=&quot;algo-nav-btn accent&quot; data-algo=&quot;heap&quot; onclick=&quot;selectAlgo(&#39;heap&#39;)&quot;&gt;Heap O(n log n)&lt;/button&gt;
    &lt;button class=&quot;algo-nav-btn green&quot; data-algo=&quot;counting&quot; onclick=&quot;selectAlgo(&#39;counting&#39;)&quot;&gt;Counting O(n+k)&lt;/button&gt;
  &lt;/div&gt;
  &lt;div class=&quot;controls&quot;&gt;
    &lt;button class=&quot;cyber-btn&quot; id=&quot;btn-generate&quot;&gt;&lt;i class=&quot;fa-solid fa-rotate&quot;&gt;&lt;/i&gt; 新資料&lt;/button&gt;
    &lt;button class=&quot;cyber-btn primary&quot; id=&quot;btn-run&quot;&gt;&lt;i class=&quot;fa-solid fa-play&quot;&gt;&lt;/i&gt; 執行&lt;/button&gt;
    &lt;button class=&quot;cyber-btn danger&quot; id=&quot;btn-stop&quot; disabled&gt;&lt;i class=&quot;fa-solid fa-stop&quot;&gt;&lt;/i&gt; 終止&lt;/button&gt;
    &lt;label class=&quot;cyber-btn sound-toggle&quot; id=&quot;sound-label&quot;&gt;
      &lt;input type=&quot;checkbox&quot; id=&quot;sound-check&quot; checked style=&quot;display:none&quot;&gt;
      &lt;i class=&quot;fa-solid fa-volume-high&quot;&gt;&lt;/i&gt; 音效 ON
    &lt;/label&gt;
  &lt;/div&gt;
  &lt;div class=&quot;status-panel&quot; id=&quot;status-panel&quot;&gt;System Standby... // Choose an Algorithm&lt;/div&gt;
  &lt;div class=&quot;array-container&quot; id=&quot;array-container&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;jump-section-btn-wrap&quot;&gt;
    &lt;a id=&quot;jump-section-link&quot; href=&quot;#bubble-section&quot; class=&quot;cyber-btn primary&quot;&gt;📖 跳轉到 Bubble Sort 說明&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="演算法" scheme="https://boycework.eu.cc/categories/%E6%BC%94%E7%AE%97%E6%B3%95/"/>
    
    <category term="Algorithm" scheme="https://boycework.eu.cc/categories/%E6%BC%94%E7%AE%97%E6%B3%95/Algorithm/"/>
    
    <category term="C++" scheme="https://boycework.eu.cc/categories/%E6%BC%94%E7%AE%97%E6%B3%95/Algorithm/C/"/>
    
    <category term="競程" scheme="https://boycework.eu.cc/categories/%E6%BC%94%E7%AE%97%E6%B3%95/Algorithm/C/%E7%AB%B6%E7%A8%8B/"/>
    
    <category term="教學" scheme="https://boycework.eu.cc/categories/%E6%BC%94%E7%AE%97%E6%B3%95/Algorithm/C/%E7%AB%B6%E7%A8%8B/%E6%95%99%E5%AD%B8/"/>
    
    
    <category term="演算法" scheme="https://boycework.eu.cc/tags/%E6%BC%94%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>115 特殊選才心得｜資工特選的現實與轉彎</title>
    <link href="https://boycework.eu.cc/2025/12/24/115-special/"/>
    <id>https://boycework.eu.cc/2025/12/24/115-special/</id>
    <published>2025-12-24T02:00:00.000Z</published>
    <updated>2026-03-09T15:08:23.810Z</updated>
    
    <content type="html"><![CDATA[<h2 id="🎄-平安夜的一份聖誕禮物"><a href="#🎄-平安夜的一份聖誕禮物" class="headerlink" title="🎄 平安夜的一份聖誕禮物"></a>🎄 平安夜的一份聖誕禮物</h2><p>今天是平安夜。<br>這篇文章，當作我送給所有正在準備、或即將踏上特殊選才這條路的學弟妹的一份聖誕禮物。</p><p>沒有雞湯，沒有過度美化，<br>只有一個真實走過 115 資工特選的人，把一路上的現實、心態崩潰與轉彎，完整寫下來。</p><p>如果你現在也正在等結果、懷疑自己，<br>希望這篇能讓你知道：你不是唯一一個撐得很辛苦的人。</p><hr><h2 id="📊-115特殊選才報名學校與結果-沒人權了"><a href="#📊-115特殊選才報名學校與結果-沒人權了" class="headerlink" title="📊 115特殊選才報名學校與結果 (沒人權了)"></a>📊 115特殊選才報名學校與結果 (沒人權了)</h2><ul><li>清大 刷掉</li><li>交大 刷掉</li><li>成大 備取</li><li>中央 刷掉</li><li>中興 正取</li><li>暨南 備取(備上放棄)</li><li>高師 正取(放棄)</li><li>逢甲 備取(備上放棄)</li></ul><hr><h2 id="放榜前的真實心態：其實真的很難撐"><a href="#放榜前的真實心態：其實真的很難撐" class="headerlink" title="放榜前的真實心態：其實真的很難撐"></a>放榜前的真實心態：其實真的很難撐</h2><p>老實說，在特殊選才放榜前那段時間，我的心態非常不穩。</p><p>不是因為我沒準備，而是「連續被一階刷掉」這件事，本身就很消耗人。<br>很多學校連二階都沒進，只在系統上看到一句冷冰冰的「未通過」，卻不知道問題到底出在哪裡。</p><p>那段時間，我反覆出現這些念頭：</p><ul><li>是不是我高估了自己？</li><li>是不是我做的專題其實根本不被看重？</li><li>是不是走特殊選才，本來就是一個錯誤？</li></ul><p>即使理性上知道「資工特選本來就極度競爭」，<br>但當結果一次又一次出來，要保持冷靜，其實非常困難。</p><hr><h2 id="一階篩選帶來的現實感"><a href="#一階篩選帶來的現實感" class="headerlink" title="一階篩選帶來的現實感"></a>一階篩選帶來的現實感</h2><p>這次特殊選才，讓我非常清楚地感受到一件事：</p><p><strong>資工相關科系的一階門檻，真的非常高。</strong></p><p>不論你做過多少專題、寫過多少程式，只要在量化指標上沒有站在前段班——<br>例如 APCS 成績、資奧相關經歷、或是非常亮眼的全國競賽名次，<br>很多學校在第一階段就會直接刷掉。</p><p>這不代表你不努力，也不代表你沒能力，<br>而是資工特選的競爭，本來就集中在極少數頂尖背景的人身上。</p><hr><h2 id="心態調整：我到底在證明什麼？"><a href="#心態調整：我到底在證明什麼？" class="headerlink" title="心態調整：我到底在證明什麼？"></a>心態調整：我到底在證明什麼？</h2><p>在那段每天都在等結果的時間裡，我後來逼自己冷靜下來，問了自己一個問題：</p><p><strong>我是想證明自己，還是真的想找一個能繼續累積能力的地方？</strong></p><p>想清楚之後，心態反而慢慢穩定下來。<br>我開始接受「不是每一條路都一定要從資工系開始」，<br>也開始認真去看課程結構，而不是只盯著系名。</p><hr><h2 id="給想走資工特殊選才的一句實話"><a href="#給想走資工特殊選才的一句實話" class="headerlink" title="給想走資工特殊選才的一句實話"></a>給想走資工特殊選才的一句實話</h2><p>這是我親身走過後，非常真心的一段建議。</p><p>如果你 <strong>沒有 APCS 4 級分以上</strong>，<br>也 <strong>沒有資奧或非常亮眼的全國競賽背景</strong>，<br>想走資工特殊選才，真的要有非常強的心理準備。</p><p>這條路不只競爭激烈，<br>而是會長時間消耗你的心態與自信。</p><p>如果你的條件比較接近學測體制，<br>乖乖準備學測，未必是比較差的選擇。</p><hr><h2 id="📄-三折頁真的大加分"><a href="#📄-三折頁真的大加分" class="headerlink" title="📄 三折頁真的大加分"></a>📄 三折頁真的大加分</h2><p>這一點我一定要特別說。</p><p><strong>三折頁在二階面試時，真的非常加分。</strong></p><p>它可以幫教授在極短時間內：</p><ul><li>快速理解你的背景</li><li>記住你這個人</li><li>面試時更有依據提問</li></ul><p>很多教授其實會一邊翻、一邊問，<br>有準備三折頁，真的差很多。</p><h2 id=""><a href="#" class="headerlink" title=""></a><img src="/images/trifold/1.png" alt="三折頁範例"><br><img src="/images/trifold/2.png" alt="三折頁範例"></h2><h2 id="🎤-面試時，一定要注意的細節"><a href="#🎤-面試時，一定要注意的細節" class="headerlink" title="🎤 面試時，一定要注意的細節"></a>🎤 面試時，一定要注意的細節</h2><p>再來是面試當下的「做人基本功」，但很多人會忽略。</p><ul><li><strong>一定要等教授講完話再回答</strong></li><li>即使教授講話慢、時間有限，也請體諒</li><li>記得你面對的是長輩與學者，不是比賽評審</li></ul><p>我知道你可能會很緊張、很想把話一次講完，<br>但禮貌與態度，本身就是評分的一部分。</p><hr><h2 id="Discord-群組：非常重要的自我定位工具"><a href="#Discord-群組：非常重要的自我定位工具" class="headerlink" title="Discord 群組：非常重要的自我定位工具"></a>Discord 群組：非常重要的自我定位工具</h2><p>如果你未來有打算走特殊選才，<br>我真的非常推薦一定要進入特殊選才相關的 Discord 群組。</p><p>你會看到來自不同學校、不同領域的強者，<br>很多人真的是所謂的「電神」。</p><p>但也正因為如此，你會更清楚知道：</p><ul><li>自己大概落在哪個區間？</li><li>要不要 all in？</li><li>還是該保留退路？</li></ul><p>這個自我定位，比盲目投入還重要。<br><a href="https://discord.gg/">Discord 群組連結</a></p><hr><h2 id="關於-all-in-與退路"><a href="#關於-all-in-與退路" class="headerlink" title="關於 all in 與退路"></a>關於 all in 與退路</h2><p>我一開始，其實是打算 <strong>all in 特殊選才</strong>。</p><p>但隨著一間又一間學校在一階被刷掉，<br>心態真的會開始動搖。</p><p>後來我選擇把心態調整回來，同步準備學測。<br>但老實說，大概到十一月才全力拉回學測，其實已經非常困難。</p><p><strong>如果你要 all in，請非常清楚風險；<br>如果要保留退路，請越早開始越好。</strong></p><hr><h2 id="不要省特殊選才的錢"><a href="#不要省特殊選才的錢" class="headerlink" title="不要省特殊選才的錢"></a>不要省特殊選才的錢</h2><p>這件事很現實，但一定要說。</p><p>整個特殊選才下來，報名費大約 <strong>1～1.5 萬元</strong>。<br>這不是一個小數目。</p><p>我很感謝家裡、尤其是媽媽，一直支持我嘗試。<br>沒有這份支持，其實很可能中途就被迫停下來。</p><p>特殊選才，不只是學生一個人的事，<br>而是整個家庭一起承擔風險。</p><hr><h2 id="結語"><a href="#結語" class="headerlink" title="結語"></a>結語</h2><p>回頭看這段特殊選才的過程，我不會說它輕鬆，<br>但我也不後悔。</p><p>它讓我很清楚地看見自己的位置，<br>也讓我學會在不理想的結果裡，重新為自己做選擇。</p><p>最後進入 <strong>中興大學應用數學系（數據科學與計算組）</strong>，<br>對我來說不是退而求其次，<br>而是一個重新出發的起點。</p><p>如果你正在這條路上，<br>希望你能撐住，<br>也希望你不要因為一時的結果，否定整個努力的自己。</p><p>我之後會發各校有進入面試的心得感想喔!</p><p>🎄 平安夜，祝你聖誕快樂。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;🎄-平安夜的一份聖誕禮物&quot;&gt;&lt;a href=&quot;#🎄-平安夜的一份聖誕禮物&quot; class=&quot;headerlink&quot; title=&quot;🎄 平安夜的一份聖誕禮物&quot;&gt;&lt;/a&gt;🎄</summary>
        
      
    
    
    
    <category term="心得" scheme="https://boycework.eu.cc/categories/%E5%BF%83%E5%BE%97/"/>
    
    
    <category term="心得" scheme="https://boycework.eu.cc/tags/%E5%BF%83%E5%BE%97/"/>
    
  </entry>
  
  <entry>
    <title>2025 AIS3 Junior Writeup｜從基礎 Web 到 XSS 與後端漏洞</title>
    <link href="https://boycework.eu.cc/2025/08/13/AIS3-Full-Security-Record/"/>
    <id>https://boycework.eu.cc/2025/08/13/AIS3-Full-Security-Record/</id>
    <published>2025-08-13T02:00:00.000Z</published>
    <updated>2026-03-09T15:08:23.773Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>這篇文章紀錄我在 <strong>2025 AIS3 Junior</strong> 中的學習歷程，從基礎 Web 題目開始，一路接觸到前端 XSS 與後端常見漏洞（PHP、SQL Injection、SSRF 等）。</p><p>這份內容除了當下的解題紀錄，也整理了我對每一類題目的理解與反思，作為自己之後複習 Web Security 與 CTF 的學習筆記。</p><hr><h2 id="一、基礎-Web-題目：培養觀察力"><a href="#一、基礎-Web-題目：培養觀察力" class="headerlink" title="一、基礎 Web 題目：培養觀察力"></a>一、基礎 Web 題目：培養觀察力</h2><h3 id="GET-aHEAD-—-HTTP-Method-的差異"><a href="#GET-aHEAD-—-HTTP-Method-的差異" class="headerlink" title="GET aHEAD — HTTP Method 的差異"></a>GET aHEAD — HTTP Method 的差異</h3><p>這題讓我第一次明確感受到，<strong>HTTP Method 本身就可能是漏洞來源</strong>。</p><p>我使用 Burp Suite 攔截請求，將原本的 <code>POST</code> 改成 <code>HEAD</code>，結果在回應標頭中直接看到 flag。</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">HEAD</span> <span class="string">/index.php</span> <span class="meta">HTTP/1.1</span></span><br></pre></td></tr></table></figure><p>這題讓我學到：<br>有些伺服器會把敏感資訊放在 header，而 <code>HEAD</code> 請求因為不回傳 body，反而更容易被忽略。</p><hr><h3 id="Cookies-—-修改-Cookie-value"><a href="#Cookies-—-修改-Cookie-value" class="headerlink" title="Cookies — 修改 Cookie value"></a>Cookies — 修改 Cookie value</h3><p>在瀏覽器中開啟 <code>F12 → Application → Cookies</code>，可以直接修改 Cookie 的 value。這題透過不斷嘗試不同數值，最後在特定值時直接顯示 flag。</p><p>這提醒我：<br><strong>只要沒有在後端驗證，Cookie 就永遠不能被信任。</strong></p><hr><h3 id="Inspect-HTML-Web-Inspector"><a href="#Inspect-HTML-Web-Inspector" class="headerlink" title="Inspect HTML &#x2F; Web Inspector"></a>Inspect HTML &#x2F; Web Inspector</h3><p>這幾題都圍繞在一個核心概念：</p><blockquote><p>使用者看到的畫面，往往只是實際 HTML 與 DOM 的一小部分。</p></blockquote><p>包含：</p><ul><li>HTML 註解中藏 flag</li><li>DOM 深層節點藏 Base64 編碼字串</li><li>將 Base64 解碼後取得 flag</li></ul><p>也再次驗證了一件事：<strong>Web 題目一定要先打開 F12 看一輪。</strong></p><hr><h2 id="二、Linux-基礎操作：CTF-的基本功"><a href="#二、Linux-基礎操作：CTF-的基本功" class="headerlink" title="二、Linux 基礎操作：CTF 的基本功"></a>二、Linux 基礎操作：CTF 的基本功</h2><p>在 Linux 題目中，大多數考的不是複雜技巧，而是對基本指令的熟悉度，例如：</p><ul><li><code>ls -la</code>：查看隱藏檔案</li><li><code>cat</code>：讀取檔案內容</li><li><code>touch</code>：建立檔案</li><li><code>mv</code>：移動檔案</li><li><code>rm</code>：刪除檔案</li></ul><p>這些指令看似簡單，但在 CTF 中就是最重要的「肌肉記憶」。</p><hr><h2 id="三、前端題目：XSS-攻擊實作"><a href="#三、前端題目：XSS-攻擊實作" class="headerlink" title="三、前端題目：XSS 攻擊實作"></a>三、前端題目：XSS 攻擊實作</h2><h3 id="Stored-XSS-—-Note-Sharing"><a href="#Stored-XSS-—-Note-Sharing" class="headerlink" title="Stored XSS — Note Sharing"></a>Stored XSS — Note Sharing</h3><p>當使用者輸入的內容沒有經過過濾就被直接渲染成 HTML，就可能導致 Stored XSS。</p><p>透過插入 <code>&lt;script&gt;</code>，確認可以執行 JavaScript 後，再將 cookie 透過 <code>fetch</code> 傳送到自己的 webhook。</p><p>這類題目讓我清楚理解：<br><strong>只要惡意內容會被存起來並再次顯示，就有機會影響其他使用者。</strong></p><hr><h3 id="DOM-XSS-—-innerHTML"><a href="#DOM-XSS-—-innerHTML" class="headerlink" title="DOM XSS — innerHTML"></a>DOM XSS — innerHTML</h3><p>這題使用 <code>innerHTML</code> 將使用者輸入直接插入 DOM，導致事件屬性（如 <code>onerror</code>）仍可執行。</p><p>即使不能直接使用 <code>&lt;script&gt;</code>，仍可以透過圖片錯誤事件來觸發 JavaScript。</p><hr><h3 id="javascript-URL-與-利用"><a href="#javascript-URL-與-利用" class="headerlink" title="javascript: URL 與 &lt;base&gt; 利用"></a>javascript: URL 與 <code>&lt;base&gt;</code> 利用</h3><p>在某些題目中，使用者輸入會被放入按鈕連結或相對路徑中：</p><ul><li><code>javascript:</code> URL 可在點擊時執行程式碼</li><li><code>&lt;base&gt;</code> 標籤可以改變整個頁面的相對路徑解析方式</li></ul><p>這些技巧讓我理解到：<br><strong>XSS 不只存在於 <code>&lt;script&gt;</code>，而是存在於所有「可被解譯的位置」。</strong></p><hr><h2 id="四、後端漏洞：邏輯錯誤比技巧更重要"><a href="#四、後端漏洞：邏輯錯誤比技巧更重要" class="headerlink" title="四、後端漏洞：邏輯錯誤比技巧更重要"></a>四、後端漏洞：邏輯錯誤比技巧更重要</h2><h3 id="PHP-鬆散比較（Loose-Comparison）"><a href="#PHP-鬆散比較（Loose-Comparison）" class="headerlink" title="PHP 鬆散比較（Loose Comparison）"></a>PHP 鬆散比較（Loose Comparison）</h3><p>透過陣列傳參，讓 <code>strcmp</code> 與 <code>md5</code> 發生型別錯誤回傳 <code>null</code>，再搭配鬆散比較 (<code>==</code>) 繞過條件判斷。</p><p>這類題目不需要高深技巧，只要理解 PHP 的型別系統就能突破。</p><hr><h3 id="檔案上傳與-WAF-繞過"><a href="#檔案上傳與-WAF-繞過" class="headerlink" title="檔案上傳與 WAF 繞過"></a>檔案上傳與 WAF 繞過</h3><p>利用：</p><ul><li>偽造 <code>Content-Type</code></li><li>副檔名大小寫差異（<code>.phP</code>）</li></ul><p>即可繞過簡單檢查並成功上傳可執行檔案。</p><hr><h3 id="LFI、SQL-Injection-與-SSRF"><a href="#LFI、SQL-Injection-與-SSRF" class="headerlink" title="LFI、SQL Injection 與 SSRF"></a>LFI、SQL Injection 與 SSRF</h3><p>後端題目涵蓋：</p><ul><li>路徑回跳繞過白名單</li><li><code>include()</code> 導致的 LFI 轉 RCE</li><li>UNION-based SQL Injection</li><li>利用 open redirect 繞過 SSRF 主機名檢查</li></ul><p>這些題目讓我最大的收穫是：<br><strong>很多漏洞並不是因為少了 WAF，而是因為「邏輯假設錯誤」。</strong></p><hr><h2 id="收穫與反思"><a href="#收穫與反思" class="headerlink" title="收穫與反思"></a>收穫與反思</h2><p>這次 2025 AIS3 Junior 對我來說，不只是解題，而是一個完整的 Web Security 入門體驗。</p><p>我學到的關鍵包括：</p><ul><li>永遠不要相信使用者輸入</li><li>前端與後端的每一個假設，都可能成為漏洞</li><li>比起背 payload，更重要的是理解程式怎麼「想錯了」</li></ul><p>這些經驗也讓我更確定，未來想繼續往資安與系統安全方向深入學習。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;這篇文章紀錄我在 &lt;strong&gt;2025 AIS3 Junior&lt;/strong&gt; 中的學習歷程，從基礎 Web</summary>
        
      
    
    
    
    <category term="Writeup" scheme="https://boycework.eu.cc/categories/Writeup/"/>
    
    <category term="2025 AIS3 Junior" scheme="https://boycework.eu.cc/categories/Writeup/2025-AIS3-Junior/"/>
    
    
  </entry>
  
  <entry>
    <title>Filename Confusion | Writeup</title>
    <link href="https://boycework.eu.cc/2025/08/13/250813-filename-confusion/"/>
    <id>https://boycework.eu.cc/2025/08/13/250813-filename-confusion/</id>
    <published>2025-08-12T16:00:00.000Z</published>
    <updated>2026-03-09T15:08:23.795Z</updated>
    
    <content type="html"><![CDATA[<hr><h2 id="Apache-是什麼？模組是幹嘛？"><a href="#Apache-是什麼？模組是幹嘛？" class="headerlink" title="Apache 是什麼？模組是幹嘛？"></a>Apache 是什麼？模組是幹嘛？</h2><blockquote><p>Apache 是<strong>高度模組化</strong>的 Web 伺服器；處理一個 HTTP 請求時，很多模組會共同讀寫 <code>request_rec</code> 的欄位（如 <code>r-&gt;filename</code>、<code>r-&gt;args</code>）。<strong>不同模組對同一欄位的「語意」若不一致，就可能誤判</strong>。Orange Tsai 的研究把這些不一致系統化，歸納出三大類 Confusion 攻擊，其中之一就是 <strong>Filename Confusion</strong>。</p></blockquote><p><strong>Filename Confusion 的核心</strong>：</p><blockquote><p>多個模組對 <code>r-&gt;filename</code> 的理解不同——有的把它當<strong>檔案路徑</strong>，有的把它當<strong>URL</strong>。這種不一致會造成安全問題。</p></blockquote><p><strong>關鍵：URL 編碼 <code>%3F</code></strong></p><blockquote><ul><li>在網址裡 <code>?</code> 代表「後面是 query string」。</li><li>如果要在網址中表示字面上的 <code>?</code>，需要 URL 編碼：<code>?</code> → <code>%3F</code>。</li><li>Apache 在不同處理階段可能把 <code>%3F</code> 解回 <code>?</code>，這就是漏洞能被利用的<strong>時機差</strong>。</li></ul></blockquote><hr><h2 id="1-RewriteRule（mod-rewrite）"><a href="#1-RewriteRule（mod-rewrite）" class="headerlink" title="1. RewriteRule（mod_rewrite）"></a>1. RewriteRule（<code>mod_rewrite</code>）</h2><p><code>RewriteRule</code> 是 <code>mod_rewrite</code> 用來<strong>改寫 URL</strong>的指令。語法：</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">RewriteRule</span> Pattern Substitution<span class="meta"> [Flags]</span></span><br></pre></td></tr></table></figure><blockquote><ul><li><strong>Pattern（樣式）</strong>：正則表達式，用來比對目前請求的 URL 路徑。</li><li><strong>Substitution（替換結果）</strong>：匹配後要改到哪裡（可為檔案路徑或 URL）。可用 <code>$1</code>、<code>$2</code> 這類反向引用插入 Pattern 抓到的內容。</li><li><strong>Flags（旗標，可選）</strong>：放在 <code>[]</code> 內，調整這條規則的行為，例如：</li></ul></blockquote><blockquote><blockquote><p><code>L</code>：命中後停止再處理後續規則（Last）。<br><code>QSA</code>：保留原始 query string 並附加到新 URL。<br><code>H=…</code>：指定處理器（Handler），例如 <code>H=application/x-httpd-php</code>。</p></blockquote></blockquote><h3 id="小例子（Pattern-Substitution-Flags）"><a href="#小例子（Pattern-Substitution-Flags）" class="headerlink" title="小例子（Pattern &#x2F; Substitution &#x2F; Flags）"></a>小例子（Pattern &#x2F; Substitution &#x2F; Flags）</h3><blockquote><p>想把 <code>https://example.com/user/orange</code> 對應到伺服器檔案 <code>/var/www/user/orange/profile.yml</code>：</p></blockquote><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">RewriteEngine</span> <span class="literal">On</span></span><br><span class="line"><span class="attribute">RewriteRule</span> <span class="string">&quot;^/user/(.+)$&quot;</span> <span class="string">&quot;/var/www/user/$1/profile.yml&quot;</span></span><br></pre></td></tr></table></figure><blockquote><ul><li>Pattern：<code>^/user/(.+)$</code> 會抓到 <code>/user/</code> 後面的使用者名稱到 <code>$1</code>。</li><li>Substitution：<code>/var/www/user/$1/profile.yml</code> → 例如 <code>$1=orange</code> 就是 <code>/var/www/user/orange/profile.yml</code>。</li><li>這裡沒寫 Flags，使用預設行為。</li></ul></blockquote><hr><h2 id="1‑1-1-Path-Truncation（路徑截斷）"><a href="#1‑1-1-Path-Truncation（路徑截斷）" class="headerlink" title="1‑1-1 Path Truncation（路徑截斷）"></a>1‑1-1 Path Truncation（路徑截斷）</h2><p><strong>成因</strong>：</p><blockquote><p><code>mod_rewrite</code> 在套用 RewriteRule 後，會把<strong>替換結果</strong>當「URL」處理，呼叫 <code>splitout_queryargs()</code> 把 <code>?</code> 後的部分視為 query 分離。當替換結果其實是<strong>檔案路徑</strong>時，還是會被「當 URL 來切」，於是產生<strong>可控截斷</strong>。</p></blockquote><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">RewriteEngine</span> <span class="literal">On</span></span><br><span class="line"><span class="attribute">RewriteRule</span> <span class="string">&quot;^/user/(.+)$&quot;</span> <span class="string">&quot;/var/user/$1/profile.yml&quot;</span></span><br><span class="line"><span class="comment"># 攻擊請求（%2F = /, %3F = ?）</span></span><br><span class="line"><span class="comment"># /user/orange%2Fsecret.yml%3F  → 讀到 /var/user/orange/secret.yml</span></span><br></pre></td></tr></table></figure><blockquote><p>因為 <code>%3F</code> 在後續階段被視為 <code>?</code>，<code>/profile.yml</code> 被當作 query 切掉了。</p></blockquote><h3 id="splitout-queryargs-在幹嘛？"><a href="#splitout-queryargs-在幹嘛？" class="headerlink" title="splitout_queryargs() 在幹嘛？"></a><code>splitout_queryargs()</code> 在幹嘛？</h3><blockquote><p>它把替換結果中的 <code>?</code> 後段剝離到 <code>r-&gt;args</code>（query），<code>?</code> 前面留在 <code>r-&gt;filename</code>：</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">r-&gt;filename = &quot;/var/user/orange/profile.yml?debug=1&quot;</span><br><span class="line">               └─── 路徑 ───┘ └─ query ─┘</span><br><span class="line">r-&gt;args     = &quot;debug=1&quot;   ← 被 splitout_queryargs() 剝出去</span><br></pre></td></tr></table></figure><hr><h2 id="1‑1‑2-Mislead-RewriteFlag-Assignment（誤導旗標設定）"><a href="#1‑1‑2-Mislead-RewriteFlag-Assignment（誤導旗標設定）" class="headerlink" title="1‑1‑2 Mislead RewriteFlag Assignment（誤導旗標設定）"></a>1‑1‑2 Mislead RewriteFlag Assignment（誤導旗標設定）</h2><p><strong>利用點</strong>：</p><blockquote><p><strong>先 Pattern 比對 → 套 Flags → 再把結果當 URL 拆 query</strong>。</p></blockquote><blockquote><p>在<strong>比對階段</strong>使用 <code>%3F</code>（編碼的 <code>?</code>）讓請求看起來像是 <code>.php</code>，使規則套上不該有的 Flag（例如指定 PHP Handler）。等到<strong>拆 query</strong>之後，實際要處理的檔案變回 <code>.gif</code>，但 <strong>Flag 不會被移除</strong>，導致圖片被當作 PHP 來跑。</p></blockquote><h3 id="規則長這樣"><a href="#規則長這樣" class="headerlink" title="規則長這樣"></a>規則長這樣</h3><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">RewriteEngine</span> <span class="literal">On</span></span><br><span class="line"><span class="attribute">RewriteRule</span> ^(.+\.php)$ $<span class="number">1</span><span class="meta"> [H=application/x-httpd-php]</span></span><br></pre></td></tr></table></figure><h3 id="攻擊步驟"><a href="#攻擊步驟" class="headerlink" title="攻擊步驟"></a>攻擊步驟</h3><ol><li><p>上傳看似普通的圖片 <code>/upload/1.gif</code>，實際內容藏有 PHP<br>例如：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span> <span class="keyword">echo</span> <span class="title function_ invoke__">shell_exec</span>(<span class="string">&#x27;id&#x27;</span>); <span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>送出請求：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/upload/1.gif%3Faaa.php</span><br></pre></td></tr></table></figure></li><li><p><strong>Pattern 比對</strong>：<code>%3F</code> 尚未解碼，<code>/upload/1.gif%3Faaa.php</code> 尾端看起來是 <code>.php</code> → 命中 → 套上 <code>H=application/x-httpd-php</code>。</p></li><li><p><strong>拆 query</strong>：<code>%3F</code> 解為 <code>?</code>，<code>?aaa.php</code> 被分離到 <code>r-&gt;args</code>，留下 <code>/upload/1.gif</code>。</p></li><li><p><strong>實際處理</strong>：雖然是 <code>.gif</code>，但已指定 PHP Handler → 由 PHP 執行圖片內的程式碼。</p></li></ol><h3 id="對照表"><a href="#對照表" class="headerlink" title="對照表"></a>對照表</h3><table><thead><tr><th>步驟</th><th>正常（安全）</th><th>漏洞利用（1‑1‑2）</th></tr></thead><tbody><tr><td>1. 上傳檔案</td><td>只能上傳圖片；<code>.php</code> 拒絕</td><td>上傳 <code>.gif</code>（內藏 PHP）通過檢查</td></tr><tr><td>2. 發送請求</td><td><code>/upload/1.gif</code> → 回傳圖片</td><td><code>/upload/1.gif%3Faaa.php</code></td></tr><tr><td>3. Rewrite 比對</td><td><code>.gif</code> 不匹配 <code>^(.+\.php)$</code>，不套 Flag</td><td><code>%3Faaa.php</code> 讓 Pattern 命中，套 <code>H=application/x-httpd-php</code></td></tr><tr><td>4. <code>splitout_queryargs</code></td><td>無變化</td><td><code>%3F</code>→<code>?</code>；<code>?aaa.php</code> 被拆到 <code>r-&gt;args</code>，目標成 <code>/upload/1.gif</code></td></tr><tr><td>5. 處理器</td><td>靜態檔案處理器</td><td>仍由 PHP Handler 處理 <code>.gif</code></td></tr><tr><td>6. 結果</td><td>顯示圖片</td><td>圖片被當 PHP 執行（RCE 風險）</td></tr></tbody></table><hr><h2 id="1-2-ACL（Access-Control-List）與-PHP‑FPM"><a href="#1-2-ACL（Access-Control-List）與-PHP‑FPM" class="headerlink" title="1-2. ACL（Access Control List）與 &lt;Files&gt; &#x2F; PHP‑FPM"></a>1-2. ACL（Access Control List）與 <code>&lt;Files&gt;</code> &#x2F; PHP‑FPM</h2><h3 id="要先了解以下三個角色"><a href="#要先了解以下三個角色" class="headerlink" title="要先了解以下三個角色:"></a>要先了解以下三個角色:</h3><blockquote><ul><li><strong><code>mod_proxy</code></strong>：代理模組，負責把請求轉送到別的服務。</li></ul></blockquote><blockquote><blockquote><p>  <code>mod_proxy_fcgi</code>：把請求用 FastCGI 協定轉給 PHP‑FPM。</p></blockquote><ul><li><strong>PHP‑FPM</strong>：<code>PHP FastCGI Process Manager</code>，Apache 把 <code>.php</code> 請求交給它執行。</li><li><strong><code>&lt;Files&gt;</code>（ACL）</strong>：針對「檔案名稱」設定限制，例如特定檔案需登入才可訪問。</li></ul></blockquote><h3 id="常見把-php-交給-PHP‑FPM-的做法"><a href="#常見把-php-交給-PHP‑FPM-的做法" class="headerlink" title="常見把 .php 交給 PHP‑FPM 的做法"></a>常見把 <code>.php</code> 交給 PHP‑FPM 的做法</h3><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">&lt;FilesMatch <span class="string">&quot;.+\.ph(?:ar|p|tml)$&quot;</span>&gt;</span></span><br><span class="line">  <span class="attribute">SetHandler</span> <span class="string">&quot;proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost&quot;</span></span><br><span class="line"><span class="section">&lt;/FilesMatch&gt;</span></span><br></pre></td></tr></table></figure><p>意思是：</p><blockquote><p>遇到 <code>.php</code>、<code>.phtml</code>… 就<strong>不要由 Apache 直接讀檔</strong>，而是交給 <code>mod_proxy_fcgi</code> → PHP‑FPM。</p></blockquote><hr><h2 id="1‑2-1-ACL-Bypass（存取控制繞過）"><a href="#1‑2-1-ACL-Bypass（存取控制繞過）" class="headerlink" title="1‑2-1 ACL Bypass（存取控制繞過）"></a>1‑2-1 ACL Bypass（存取控制繞過）</h2><blockquote><p><strong>情境</strong>：用 <code>&lt;Files&gt;</code> 來保護單一檔案，例如：</p></blockquote><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">&lt;Files <span class="string">&quot;admin.php&quot;</span>&gt;</span></span><br><span class="line">  <span class="attribute">AuthType</span> Basic</span><br><span class="line">  <span class="attribute">AuthName</span> <span class="string">&quot;Admin Panel&quot;</span></span><br><span class="line">  <span class="attribute">AuthUserFile</span> <span class="string">&quot;/etc/apache2/.htpasswd&quot;</span></span><br><span class="line">  <span class="attribute">Require</span> valid-user</span><br><span class="line"><span class="section">&lt;/Files&gt;</span></span><br></pre></td></tr></table></figure><h3 id="正常流程（安全）"><a href="#正常流程（安全）" class="headerlink" title="正常流程（安全）"></a>正常流程（安全）</h3><ol><li>請求 <code>/admin.php</code>。</li><li>ACL 檢查：<code>r-&gt;filename = admin.php</code> → 命中 <code>&lt;Files &quot;admin.php&quot;&gt;</code> → 要求驗證。</li><li>驗證通過後，交給 PHP‑FPM 執行 <code>admin.php</code>。</li></ol><h3 id="漏洞流程（繞過）"><a href="#漏洞流程（繞過）" class="headerlink" title="漏洞流程（繞過）"></a>漏洞流程（繞過）</h3><ol><li><p>請求：<code>/admin.php%3Fooo.php</code>（<code>%3F</code> 是 <code>?</code>）</p></li><li><p><strong>ACL 檢查</strong>：此時尚未解碼 → <code>r-&gt;filename = admin.php%3Fooo.php</code>，<strong>不等於</strong> <code>admin.php</code> → 不要求驗證。</p></li><li><p><strong>SetHandler 命中</strong>：仍視為 <code>.php</code> → 交給 <code>mod_proxy_fcgi</code>。</p></li><li><p><strong>mod_proxy_fcgi 轉交</strong>：把 <code>r-&gt;filename</code> 改寫為：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">proxy:fcgi://127.0.0.1:9000/var/www/html/admin.php?ooo.php</span><br></pre></td></tr></table></figure></li><li><p><strong>PHP‑FPM 行為</strong>：</p></li></ol><blockquote><blockquote><ul><li>去掉 <code>proxy:fcgi://…</code> 前綴與主機資訊，只留 <code>/var/www/html/admin.php?ooo.php</code>。</li><li>找到第一個 <code>?</code>，<strong>截斷</strong>為 <code>/var/www/html/admin.php</code>。</li><li>實際執行 <code>admin.php</code>。</li></ul><ol start="6"><li><strong>結果</strong>：受保護的 <code>admin.php</code> 被直接執行，<strong>完全跳過 ACL</strong>。</li></ol></blockquote></blockquote><h3 id="對照表-1"><a href="#對照表-1" class="headerlink" title="對照表"></a>對照表</h3><table><thead><tr><th>步驟</th><th>正常（安全）</th><th>漏洞利用（ACL Bypass）</th></tr></thead><tbody><tr><td>1. 請求 URL</td><td><code>/admin.php</code></td><td><code>/admin.php%3Fooo.php</code></td></tr><tr><td>2. ACL 檢查（<code>&lt;Files&gt;</code>）</td><td><code>r-&gt;filename = admin.php</code> → 要驗證</td><td><code>r-&gt;filename = admin.php%3Fooo.php</code> → 覺得不需驗證</td></tr><tr><td>3. SetHandler</td><td>命中 → 交給 <code>mod_proxy_fcgi</code></td><td>同左</td></tr><tr><td>4. <code>mod_proxy_fcgi</code></td><td><code>proxy:fcgi://…/admin.php</code></td><td><code>proxy:fcgi://…/admin.php?ooo.php</code></td></tr><tr><td>5. PHP‑FPM</td><td>執行 <code>admin.php</code>（無 query）</td><td>截斷 <code>?ooo.php</code> 後仍執行 <code>admin.php</code></td></tr><tr><td>6. 結果</td><td>驗證通過者才能執行</td><td>未驗證也能執行（ACL 被繞過）</td></tr></tbody></table><blockquote><p><strong>受影響的常見保護寫法</strong>（都可能被 <code>%3F</code> 打穿）：</p></blockquote><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">&lt;Files <span class="string">&quot;php-info.php&quot;</span>&gt;</span>   </span><br><span class="line"><span class="attribute">Require</span> ip <span class="number">127.0.0.1</span>   </span><br><span class="line"><span class="section">&lt;/Files&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="section">&lt;Files <span class="string">&quot;adminer.php&quot;</span>&gt;</span>    </span><br><span class="line"><span class="attribute">Deny</span> from <span class="literal">all</span>          </span><br><span class="line"><span class="section">&lt;/Files&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="section">&lt;Files <span class="string">&quot;xmlrpc.php&quot;</span>&gt;</span>     </span><br><span class="line"><span class="attribute">Deny</span> from <span class="literal">all</span>          </span><br><span class="line"><span class="section">&lt;/Files&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="section">&lt;Files <span class="string">&quot;cron.php&quot;</span>&gt;</span>       </span><br><span class="line"><span class="attribute">Deny</span> from <span class="literal">all</span>          </span><br><span class="line"><span class="section">&lt;/Files&gt;</span></span><br></pre></td></tr></table></figure><hr><h2 id="修補辦法"><a href="#修補辦法" class="headerlink" title="修補辦法"></a>修補辦法</h2><blockquote><ul><li><strong>升級 Apache 至 2.4.60+</strong>：新版本把舊有危險行為改為預設拒絕；請勿開啟 <code>RewriteOptions UnsafeAllow3F</code>（除非完全理解風險）。</li><li><strong>避免用 <code>&lt;Files&gt;</code> 保護單一 PHP 檔</strong>（在 PHP‑FPM 架構下）。改用 <strong><code>&lt;Location&gt;</code> &#x2F; <code>&lt;LocationMatch&gt;</code></strong>（比對 URL 而非檔名），或把敏感腳本移出 <strong>DocumentRoot</strong>，只透過應用層載入。</li><li><strong>Rewrite 規則審視</strong>：不要讓「使用者可控片段」直接拼到檔案路徑；能用 <code>mod_alias</code>（<code>Alias</code>&#x2F;<code>Redirect</code>）就避免過度使用 <code>mod_rewrite</code> 做檔案映射。</li><li><strong>監控與偵測</strong>：</li></ul></blockquote><blockquote><blockquote><ul><li>日誌尋找可疑編碼：<code>%3F</code>、<code>%2F</code>；</li><li>特別是「非 <code>.php</code> 路徑 + <code>%3F...php</code>」的請求模式；</li><li>除錯時可暫時開 <code>LogLevel rewrite:trace2</code> 觀察改寫流程（完畢務必關閉）。</li></ul></blockquote></blockquote><hr><h2 id="誤區／備註"><a href="#誤區／備註" class="headerlink" title="誤區／備註"></a>誤區／備註</h2><blockquote><ul><li>不是「PHP Handler 會神奇多做什麼」，而是<strong>被錯誤套用</strong>到不該由 PHP 處理的檔案上。</li><li><code>%3F</code> 的解碼與處理<strong>發生在不同階段</strong>；理解「哪一步解、何時解」是這類漏洞的核心。</li><li>範例程式碼僅用於安全測試與理解機制，<strong>請勿</strong>在生產環境或未授權系統操作。</li></ul></blockquote><p><a href="https://blog.orange.tw/posts/2024-08-confusion-attacks-ch/">文獻來源:Orange Tsai (Orange Tsai)</a></p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;hr&gt;
&lt;h2 id=&quot;Apache-是什麼？模組是幹嘛？&quot;&gt;&lt;a href=&quot;#Apache-是什麼？模組是幹嘛？&quot; class=&quot;headerlink&quot; title=&quot;Apache 是什麼？模組是幹嘛？&quot;&gt;&lt;/a&gt;Apache</summary>
        
      
    
    
    
    <category term="資安" scheme="https://boycework.eu.cc/categories/%E8%B3%87%E5%AE%89/"/>
    
    <category term="Writeup" scheme="https://boycework.eu.cc/categories/%E8%B3%87%E5%AE%89/Writeup/"/>
    
    
    <category term="資安" scheme="https://boycework.eu.cc/tags/%E8%B3%87%E5%AE%89/"/>
    
  </entry>
  
  <entry>
    <title>拍貼機系統 P!X 印一下</title>
    <link href="https://boycework.eu.cc/2025/07/26/Photobooth_Post/"/>
    <id>https://boycework.eu.cc/2025/07/26/Photobooth_Post/</id>
    <published>2025-07-26T02:00:00.000Z</published>
    <updated>2026-03-09T15:08:23.743Z</updated>
    
    <content type="html"><![CDATA[<p>這是一套結合網站、美編與硬體控制的全自動拍貼機，專為活動現場、展覽或自助服務設計，讓使用者能輕鬆拍照、選擇模板並立即列印成品。系統整合軟硬體，流程自動化，並支援多種互動功能。</p><p><img src="/images/photobooth/%E5%9C%96%E7%89%871.png" alt="運作流程"></p><blockquote><p><a href="https://github.com/Boyce39/P-X">GitHub 連結</a></p></blockquote><hr><h2 id="🧩-系統原理與架構"><a href="#🧩-系統原理與架構" class="headerlink" title="🧩 系統原理與架構"></a>🧩 系統原理與架構</h2><p>本系統以 Python Flask 為後端主體，結合 Arduino 控制投幣與硬體訊號，並透過熱感應印表機完成現場列印。流程自動化，並以 ngrok 實現公網穿透，讓手機可即時上傳照片。</p><h3 id="主要流程"><a href="#主要流程" class="headerlink" title="主要流程"></a>主要流程</h3><ol><li><strong>投幣啟動</strong>：使用者投幣 40 元，投幣機將訊號傳給 Arduino，Arduino 透過序列埠（Serial）傳送 <code>coin_ok</code> 訊號給主機。</li><li><strong>QRCode 產生與列印</strong>：主機收到訊號後，產生唯一 Token 與專屬上傳網址，並生成 QRCode 票券（含說明、Logo、簽名等），自動列印並切紙。</li><li><strong>照片上傳與模板選擇</strong>：使用者掃描 QRCode，進入網頁上傳照片並選擇拍貼模板，支援即時預覽與互動。</li><li><strong>列印成品</strong>：完成後自動將美編照片傳送至印表機列印，現場立即取件。</li></ol><hr><h2 id="🔌-技術整合"><a href="#🔌-技術整合" class="headerlink" title="🔌 技術整合"></a>🔌 技術整合</h2><ul><li><strong>Flask 網站系統</strong>：提供照片上傳、模板選擇、即時預覽與互動介面。</li><li><strong>印表機控制</strong>：支援多台印表機（如 Xprinter、EPSON），可自動裁切。</li><li><strong>Arduino 投幣與列印指令</strong>：硬體負責投幣偵測、列印流程控制與狀態回饋。</li><li><strong>多種主題模板 + 圖層疊加處理</strong>：支援多款拍貼模板，並可自訂圖層與特效。</li><li><strong>自動 QRCode 產生與票券設計</strong>：每次投幣自動產生專屬 QRCode，票券含說明、簽名、直橫排文字、Logo。</li><li><strong>ngrok 公網穿透</strong>：自動啟動 ngrok，讓外部手機可連線上傳照片。</li><li><strong>Token 防重複機制</strong>：每張票券唯一 Token，防止重複上傳與列印。</li><li><strong>多執行緒與序列通訊</strong>：背景監控硬體訊號，並確保 Token 持久化與執行緒安全。</li></ul><hr><h2 id="🛠️-系統架構"><a href="#🛠️-系統架構" class="headerlink" title="🛠️ 系統架構"></a>🛠️ 系統架構</h2><ul><li><strong>前端</strong>：RWD 響應式網頁（HTML&#x2F;CSS&#x2F;JavaScript），支援手機掃描 QRCode 操作。</li><li><strong>後端</strong>：Python Flask，負責 API、圖片處理、列印指令下發。</li><li><strong>硬體</strong>：Arduino（投幣、訊號控制）、熱感應印表機、顯示模組。</li><li><strong>通訊</strong>：USB&#x2F;Serial 連接 Arduino 與印表機，HTTP 通訊前後端，ngrok 公網穿透。</li></ul><hr><h2 id="⚡-Arduino-與投幣機連接與偵測原理"><a href="#⚡-Arduino-與投幣機連接與偵測原理" class="headerlink" title="⚡ Arduino 與投幣機連接與偵測原理"></a>⚡ Arduino 與投幣機連接與偵測原理</h2><ul><li><strong>投幣機</strong>：將投幣訊號（透過上拉電阻完成脈衝偵測）連接至 Arduino 數位輸入腳位。</li><li><strong>Arduino 程式</strong>：偵測到投幣訊號後，透過 Serial（如 COM7, 9600 baud）傳送 <code>coin_ok</code> 給主機。</li><li><strong>主機（Flask）</strong>：背景執行緒監控 Serial 埠，收到 <code>coin_ok</code> 後自動產生 QRCode 並列印，可回傳狀態訊息給 Arduino。</li><li><strong>安全防呆</strong>：主機會記錄 Token，防止重複上傳、重複列印，異常時自動復原。</li></ul><h4 id="連接示意圖"><a href="#連接示意圖" class="headerlink" title="連接示意圖"></a>連接示意圖</h4><pre class="mermaid">graph TD;    A[投幣機] -- 投幣訊號 --> B("Arduino");    B -- "Serial (coin_ok)" --> C["主機 (Flask)"];    C -- QRCode 產生與列印 --> D[熱感應印表機];    D -- 列印完成 --> C;    C -- 狀態回傳 --> B;    B -- 控制訊號 --> A;</pre><hr><h2 id="📂-主要檔案結構"><a href="#📂-主要檔案結構" class="headerlink" title="📂 主要檔案結構"></a>📂 主要檔案結構</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── app.py               # Flask 主程式</span><br><span class="line">├── config.py            # 主程式設定</span><br><span class="line">├── static/              # 前端網頁</span><br><span class="line">│   ├── simulate_coin_page.html    # 管理端頁面</span><br><span class="line">│   ├── upload.html       # 上傳照片頁面</span><br><span class="line">├── templates/           # 模板圖層</span><br><span class="line">├── qr_codes/            # QRCode 票券</span><br><span class="line">├── uploads/             # 上傳照片</span><br></pre></td></tr></table></figure><hr><h2 id="🚀-特色與未來規劃"><a href="#🚀-特色與未來規劃" class="headerlink" title="🚀 特色與未來規劃"></a>🚀 特色與未來規劃</h2><ul><li>支援多模板、動態特效與自訂票券頁面</li><li>可加入雲端備份與下載功能</li><li>多語系介面</li><li>優化列印速度與美編演算法</li><li>增加現場狀態顯示與錯誤自動復原</li></ul><p><img src="/images/photobooth/%E5%9C%96%E7%89%872.jpg" alt="現場圖片"><br><img src="/images/photobooth/%E5%9C%96%E7%89%873.jpg" alt="現場圖片"><br><img src="/images/photobooth/%E5%9C%96%E7%89%874.jpg" alt="現場圖片"></p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;這是一套結合網站、美編與硬體控制的全自動拍貼機，專為活動現場、展覽或自助服務設計，讓使用者能輕鬆拍照、選擇模板並立即列印成品。系統整合軟硬體，流程自動化，並支援多種互動功能。&lt;/p&gt;
&lt;p&gt;&lt;img</summary>
        
      
    
    
    
    <category term="專案" scheme="https://boycework.eu.cc/categories/%E5%B0%88%E6%A1%88/"/>
    
    <category term="拍貼" scheme="https://boycework.eu.cc/categories/%E5%B0%88%E6%A1%88/%E6%8B%8D%E8%B2%BC/"/>
    
    <category term="P!X" scheme="https://boycework.eu.cc/categories/%E5%B0%88%E6%A1%88/%E6%8B%8D%E8%B2%BC/P-X/"/>
    
    
    <category term="專案" scheme="https://boycework.eu.cc/tags/%E5%B0%88%E6%A1%88/"/>
    
  </entry>
  
</feed>
