<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>BoyceLab 技術部落格</title>
  
  
  <link href="https://blog.boycelab.com/atom.xml" rel="self"/>
  
  <link href="https://blog.boycelab.com/"/>
  <updated>2026-10-13T12:31:09.671Z</updated>
  <id>https://blog.boycelab.com/</id>
  
  <author>
    <name>李柏毅 BoyceLab</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>FepPhoto 功能型平台：把顧客網站與門市流程接起來</title>
    <link href="https://blog.boycelab.com/2026/10/13/fepphoto-functional-platform/"/>
    <id>https://blog.boycelab.com/2026/10/13/fepphoto-functional-platform/</id>
    <published>2026-10-12T16:00:00.000Z</published>
    <updated>2026-10-13T12:31:09.671Z</updated>
    
    <content type="html"><![CDATA[<p>做 <a href="https://www.fepphoto.com/">FepPhoto</a> 時，我逐漸把注意力從「網站有哪些頁面」移到「一件事情如何完成」。顧客看到的是會員、預約、優惠券與底片服務入口，門市面對的卻是收件、交易與後續處理。兩邊若各自維護一套資料與判斷，介面再漂亮，也可能讓同一件事出現不同答案。</p><span id="more"></span><h2 id="共用-API，不等於共用權限"><a href="#共用-API，不等於共用權限" class="headerlink" title="共用 API，不等於共用權限"></a>共用 API，不等於共用權限</h2><p>顧客網站與門市 POS 共用需要授權的 API，由 Cloudflare Workers 處理授權、計價與訂單。共用的是服務邏輯，不是讓每個入口都能看見所有資料。前端負責呈現與輸入，重要判斷留在伺服器端，才不必讓網站與門市各寫一套價格計算。</p><p>D1 保存訂單等結構化紀錄，R2 保存檔案；紀錄與檔案分開，各自承擔不同責任。這也符合官方對 SQL 資料庫與物件儲存的區分，可參考 <a href="https://developers.cloudflare.com/workers/platform/storage-options/">Cloudflare 儲存選型文件</a>。</p><figure class="highlight text"><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><br><span class="line">          ├→ 授權 API／Workers → D1：業務紀錄</span><br><span class="line">門市 POS ─┘                  → R2：檔案</span><br></pre></td></tr></table></figure><p>這張圖只說明責任分工，不包含部署位置、管理入口或實際資料。</p><h2 id="先留下交易，再處理同步"><a href="#先留下交易，再處理同步" class="headerlink" title="先留下交易，再處理同步"></a>先留下交易，再處理同步</h2><p>門市 POS 的交易先在本機保存，再由背景同步送往服務端。這個順序關注的是：網路暫時不穩時，已完成的操作仍有紀錄可追，而不是讓一次連線失敗直接抹掉現場工作。</p><p>但「本機先存」不等於所有功能都能離線，也不代表同步永遠成功。重送、資料衝突與同步失敗仍是需要處理的問題；本機有紀錄，也不能直接視為雲端已確認。這些狀態值得被清楚區分，而不是只顯示一個含糊的成功訊息。</p><h2 id="付款與列印，都不能只看畫面"><a href="#付款與列印，都不能只看畫面" class="headerlink" title="付款與列印，都不能只看畫面"></a>付款與列印，都不能只看畫面</h2><p>付款狀態以通過驗證的綠界伺服器通知更新，不以顧客瀏覽器跳回網站作為依據。顧客可能關掉分頁，回跳也可能受網路影響；<a href="https://developers.ecpay.com.tw/2878/">綠界付款結果通知文件</a> 同樣要求驗證通知，並留意重複處理。</p><p>列印工作則排入佇列，目的是讓待處理工作不只存在於一次按鈕操作中。佇列不是「一定印得出來」的保證：設備故障與紙張問題仍可能發生，工作送出與實際完成也必須分開理解。</p><h2 id="已有架構，與尚未啟用的功能"><a href="#已有架構，與尚未啟用的功能" class="headerlink" title="已有架構，與尚未啟用的功能"></a>已有架構，與尚未啟用的功能</h2><p>會員、預約、優惠券與底片流程構成平台的服務脈絡，顧客可從公開網站查看入口與當下可用項目。本文描述的是已確認的架構，不把它包裝成所有情境都驗證完成的保證。</p><p><strong>LINE Pay 目前僅有準備工作，尚未啟用，不是已上線的付款方式。</strong> 後續完善同步與例外處理，也應視為持續工作，而非完成宣言。這次實作讓我更在意的不是功能清單長度，而是每一步出了問題，事情是否還能被找回、說明與接續。</p>]]></content>
    
    
    <summary type="html">從共用授權 API、Cloudflare Workers／D1／R2，到本機交易、付款通知與列印佇列，整理 FepPhoto 的公開架構與實作界線。</summary>
    
    
    
    <category term="技術筆記" scheme="https://blog.boycelab.com/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/"/>
    
    
    <category term="BoyceLab" scheme="https://blog.boycelab.com/tags/BoyceLab/"/>
    
    <category term="FepPhoto" scheme="https://blog.boycelab.com/tags/FepPhoto/"/>
    
    <category term="功能型網站" scheme="https://blog.boycelab.com/tags/%E5%8A%9F%E8%83%BD%E5%9E%8B%E7%B6%B2%E7%AB%99/"/>
    
    <category term="Cloudflare" scheme="https://blog.boycelab.com/tags/Cloudflare/"/>
    
    <category term="系統設計" scheme="https://blog.boycelab.com/tags/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88/"/>
    
  </entry>
  
  <entry>
    <title>最近的興趣：用 BoyceLab 做小店真正用得上的功能型網站</title>
    <link href="https://blog.boycelab.com/2026/10/13/functional-websites-business-notes/"/>
    <id>https://blog.boycelab.com/2026/10/13/functional-websites-business-notes/</id>
    <published>2026-10-12T16:00:00.000Z</published>
    <updated>2026-10-13T12:31:09.673Z</updated>
    
    <content type="html"><![CDATA[<p>最近我感興趣的，是網站能不能走出「展示作品」這個角色，成為小型商家每天用得上的工具。我以 BoyceLab 為品牌，透過 <a href="https://www.fepphoto.com/">FepPhoto</a> 與 <a href="https://www.pixel364.com/">Pixel364</a> 探索功能型網站與自動化。比起多放一段動畫，我更想知道：顧客完成操作後，店家是否真的接得住？</p><span id="more"></span><h2 id="功能型網站，從下一個動作開始"><a href="#功能型網站，從下一個動作開始" class="headerlink" title="功能型網站，從下一個動作開始"></a>功能型網站，從下一個動作開始</h2><p>形象網站回答「你是誰」，功能型網站還要回答「我現在能做什麼」。FepPhoto 的服務脈絡包含會員、預約、優惠券與底片處理；Pixel364 則把菜單、客製選項與點餐連在一起。顧客可從各自的公開網站接觸服務，實際可用項目以網站當下呈現為準。</p><p>兩個場景不同，卻讓我看見同一個問題：表單送出不是流程的終點。接下來由誰確認、如何交付、遇到例外怎麼辦，才是網站能否融入日常工作的關鍵。這也是我想繼續研究的方向，而不是先替每間店套上相同模板。</p><h2 id="先畫流程，再列功能"><a href="#先畫流程，再列功能" class="headerlink" title="先畫流程，再列功能"></a>先畫流程，再列功能</h2><p>如果要替小店規劃網站，我會先把一件常見工作拆開：顧客提出需求、店家接收、確認內容、完成服務。每一步都問三件事：</p><ul><li>哪些資訊是完成這一步真正需要的？</li><li>誰有權確認，顧客需要看見什麼狀態？</li><li>如果操作失敗，能否補救或改由人工接手？</li></ul><p>例如，預約送出不一定代表時段已確認，點餐送出也不等於付款完成。先說清楚這些差異，比先增加通知、報表或會員等級更重要。功能的價值應該回到具體情境，而不是只用清單長短衡量。</p><h2 id="自動化不是把人全部拿掉"><a href="#自動化不是把人全部拿掉" class="headerlink" title="自動化不是把人全部拿掉"></a>自動化不是把人全部拿掉</h2><p>我想探索的自動化，是把重複抄寫、狀態傳遞與固定提醒交給系統，讓人把注意力留給需要判斷的事情。但這是設計方向，不代表兩個專案已經把這些工作全部自動化。</p><p>遇到需求變更、付款疑問或設備故障，仍需要有人負責。我會把「誰接手失敗情境」放進規劃，而不是把例外藏起來。資料也應以完成服務所需為範圍；公開分享設計方法，不需要帶出顧客紀錄、交易內容或店內管理細節。</p><h2 id="我會如何界定下一步"><a href="#我會如何界定下一步" class="headerlink" title="我會如何界定下一步"></a>我會如何界定下一步</h2><p>對我而言，下一階段不是盲目增加功能，而是挑一段流程，確認它的輸入、完成條件與例外，再決定是否值得自動化。合作前也應談清楚功能範圍、後續維護與外部服務依賴，不能把「做得出介面」當成「已經可以承擔所有營運」。</p><p>目前這些專案是實作與學習的起點，不是營收提升或效率改善的成效報告。我沒有足夠證據宣稱它們替店家省下多少時間。若要評估價值，未來應先約定觀察方式，再用實際使用回饋檢查。比起先說服別人技術很厲害，我更想把一件小事做得清楚、可靠，而且願意長期維護。</p>]]></content>
    
    
    <summary type="html">從 FepPhoto 與 Pixel364 出發，記錄我對小型商家網站與自動化的思考：先理解流程，再界定功能、例外與維護責任。</summary>
    
    
    
    <category term="創業筆記" scheme="https://blog.boycelab.com/categories/%E5%89%B5%E6%A5%AD%E7%AD%86%E8%A8%98/"/>
    
    
    <category term="BoyceLab" scheme="https://blog.boycelab.com/tags/BoyceLab/"/>
    
    <category term="功能型網站" scheme="https://blog.boycelab.com/tags/%E5%8A%9F%E8%83%BD%E5%9E%8B%E7%B6%B2%E7%AB%99/"/>
    
    <category term="小型商家" scheme="https://blog.boycelab.com/tags/%E5%B0%8F%E5%9E%8B%E5%95%86%E5%AE%B6/"/>
    
    <category term="自動化" scheme="https://blog.boycelab.com/tags/%E8%87%AA%E5%8B%95%E5%8C%96/"/>
    
    <category term="產品設計" scheme="https://blog.boycelab.com/tags/%E7%94%A2%E5%93%81%E8%A8%AD%E8%A8%88/"/>
    
  </entry>
  
  <entry>
    <title>Pixel364 點餐設計：從咖啡菜單到一張說得清楚的訂單</title>
    <link href="https://blog.boycelab.com/2026/10/13/pixel364-ordering-design/"/>
    <id>https://blog.boycelab.com/2026/10/13/pixel364-ordering-design/</id>
    <published>2026-10-12T16:00:00.000Z</published>
    <updated>2026-10-13T12:31:09.674Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.pixel364.com/">Pixel364</a> 是我用咖啡場景探索功能型網站的專案。它不只展示菜單，也提供客製選項、桌邊 QR 點餐、外帶與預約入口。我關注的不是「能不能做出購物車」，而是顧客選完之後，店家能否理解這杯飲品的需求，以及接下來該怎麼處理。</p><span id="more"></span><h2 id="菜單不是一張圖片"><a href="#菜單不是一張圖片" class="headerlink" title="菜單不是一張圖片"></a>菜單不是一張圖片</h2><p>把菜單放上網，只解決了看見商品；點餐還需要把商品、可選項目與數量組合成明確內容。客製選項不應只靠自由文字表達，否則顧客以為選好了，店家卻可能收到無法判讀的需求。</p><p>我的設計思考是：先讓人選商品，再依商品提供適用選項，最後確認完整內容。並不是每杯飲品都應有相同設定，也不能為了畫面整齊，讓顧客選出無法製作的組合。這是規劃與檢查的準則，不是宣稱所有例外都已處理完成。</p><h2 id="入口不同，確認內容也不同"><a href="#入口不同，確認內容也不同" class="headerlink" title="入口不同，確認內容也不同"></a>入口不同，確認內容也不同</h2><p>桌邊 QR 的情境是「我正在店裡」，外帶則需要說清楚取餐安排；預約更不能直接等同一張已成立的餐點訂單。公開網站提供這些入口，實際開放項目與服務方式仍以網站當下顯示為準。</p><figure class="highlight text"><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">桌邊 QR／外帶入口 → 菜單 → 客製選項 → 確認內容 → 送出點餐</span><br><span class="line">預約入口         → 填寫需求 → 送出預約 → 後續確認</span><br></pre></td></tr></table></figure><p>這是顧客操作的簡化示意，不是付款或接單成功的證明。我希望確認畫面能讓人檢查商品、選項與服務方式，而不是只看到總價；送出之後也應明確區分「已提交」與「已確認」，避免讓顧客自行猜測。</p><h2 id="前端與資料服務的分工"><a href="#前端與資料服務的分工" class="headerlink" title="前端與資料服務的分工"></a>前端與資料服務的分工</h2><p>專案採用 React、Vite 與 TypeScript，搭配 Cloudflare Workers 與 D1。前端負責點餐介面與互動，Workers 承接 API，D1 保存結構化資料。平台機制上，React 前端可透過 Worker 使用服務綁定，而不是直接存取綁定資源；這點可參考 <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/react/">Cloudflare React 與 Vite 官方指南</a>。</p><p>這套技術組合不會自動保證訂單正確。選項是否有效、送出失敗如何提示、重複操作如何處理，都需要另外設計與驗證。我更願意把它看成責任分工的起點，而不是用框架名稱代替品質說明。</p><h2 id="網站可用，不等於所有整合已上線"><a href="#網站可用，不等於所有整合已上線" class="headerlink" title="網站可用，不等於所有整合已上線"></a>網站可用，不等於所有整合已上線</h2><p><strong>實際付款、退款與硬體 POS 整合尚未全部上線，仍屬待完成與驗證的範圍。</strong> 不能因為有點餐入口，就宣稱顧客已能完成所有支付與門市作業。</p><p>接下來我想優先檢查手機操作、內容確認與失敗提示，再逐步完善外部整合；這些是後續方向，不是既有成效。本文也不宣稱點餐速度或營收已經改善。這個專案讓我體會到，小店系統的難處常常不在新增按鈕，而在讓顧客與店家對同一筆需求有相同理解。</p>]]></content>
    
    
    <summary type="html">整理 Pixel364 的菜單、客製選項、桌邊 QR、外帶與預約設計，以及 React／Vite／TypeScript、Workers／D1 的分工和未完成整合。</summary>
    
    
    
    <category term="技術筆記" scheme="https://blog.boycelab.com/categories/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/"/>
    
    
    <category term="BoyceLab" scheme="https://blog.boycelab.com/tags/BoyceLab/"/>
    
    <category term="Pixel364" scheme="https://blog.boycelab.com/tags/Pixel364/"/>
    
    <category term="點餐系統" scheme="https://blog.boycelab.com/tags/%E9%BB%9E%E9%A4%90%E7%B3%BB%E7%B5%B1/"/>
    
    <category term="React" scheme="https://blog.boycelab.com/tags/React/"/>
    
    <category term="使用者體驗" scheme="https://blog.boycelab.com/tags/%E4%BD%BF%E7%94%A8%E8%80%85%E9%AB%94%E9%A9%97/"/>
    
  </entry>
  
  <entry>
    <title>FtO Seoul 2026：在 rep0rter 專案裡，和不同國家的人一起合作</title>
    <link href="https://blog.boycelab.com/2026/10/05/fto-seoul-2026-rep0rter/"/>
    <id>https://blog.boycelab.com/2026/10/05/fto-seoul-2026-rep0rter/</id>
    <published>2026-10-05T02:20:00.000Z</published>
    <updated>2026-10-05T02:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>九月，我在首爾參加 Facing the Ocean（FtO）2026，加入了 <strong>rep0rter</strong> 專案，也認識很多來自不同國家與背景的人。</p><p>如果只用一句話留下這次經驗，我之前寫的是：<strong>很好的回憶，明年日本不見不散。</strong></p><p>但除了回憶，我也想把這次活動和一般比賽、個人開發不同的地方整理下來。</p><h2 id="FtO-是什麼？先不用想著「要拿第幾名」"><a href="#FtO-是什麼？先不用想著「要拿第幾名」" class="headerlink" title="FtO 是什麼？先不用想著「要拿第幾名」"></a>FtO 是什麼？先不用想著「要拿第幾名」</h2><p>Facing the Ocean 連結臺灣、日本與韓國等地的 Civic Tech 社群。2026 首爾活動於 9 月 18 至 20 日在 Haja Center 舉行，主要協作活動在 19、20 日。</p><p>它是非競賽式的社群黑客松，沒有以排名或獎金為中心。參與者帶著公共議題、正在進行的專案與自己的經驗，來交流並一起動手。</p><p><strong>Civic Tech 可以先理解成「用科技參與公共事務」</strong>。重點不一定是某個特別複雜的技術，而是資訊能不能更容易取得、工具能不能降低參與的門檻，以及它是否真的回應了使用者的需要。</p><h2 id="加入-rep0rter：先理解已經存在的東西"><a href="#加入-rep0rter：先理解已經存在的東西" class="headerlink" title="加入 rep0rter：先理解已經存在的東西"></a>加入 rep0rter：先理解已經存在的東西</h2><p>這次我加入的專案是 rep0rter。相關開發討論涉及內容發布、Threads API，以及多平台流程。</p><p>進入一個既有專案，和從零開始做自己的作品不同。自己的專案可以依照熟悉的習慣安排；共同開發則需要先理解現在的結構、原本的需求，以及夥伴對系統的想法。</p><p>所以，「開始寫程式」不一定是第一步。先把問題與流程對齊，確認誰正在處理什麼，反而能避免大家各自做出一套互不相容的答案。</p><p>這篇是參與紀錄，不把討論過的功能當成全部已完成，也不把團隊成果寫成由我一個人完成。</p><h2 id="發布工具背後，其實都是使用者的細節"><a href="#發布工具背後，其實都是使用者的細節" class="headerlink" title="發布工具背後，其實都是使用者的細節"></a>發布工具背後，其實都是使用者的細節</h2><p>內容發布看起來可能只是呼叫一次 API，但實際討論會延伸出許多情境：</p><ul><li>新內容要怎麼發布，舊內容又怎麼處理？</li><li>重試時如何避免同一份內容重複出現？</li><li>發送前能不能先看到預覽？</li><li>如果其中一個平台失敗，使用者要怎麼知道？</li><li>自動化與人工操作之間，要保留哪些控制？</li></ul><p>這些問題也提醒我，API 串接不是「收到成功回應」就結束了。可靠的工具還需要讓人看懂狀態、知道能做什麼，以及遇到問題時有路可以走。</p><p>這和我正在整理 BoyceLab 的方向很像：功能存在是一回事，讓人容易理解與使用，是另一件必須認真完成的事。</p><h2 id="和不同背景的人一起做事"><a href="#和不同背景的人一起做事" class="headerlink" title="和不同背景的人一起做事"></a>和不同背景的人一起做事</h2><p>我在活動中認識了不同國家與背景的參與者。這次交流對我來說，不只是交換名字或社群帳號；一起討論與開發，會讓人看見別人怎麼理解問題，也需要練習說明自己的想法。</p><p>同一個需求，不同人可能會先注意不同的部分。有的人從資料出發，有的人關心介面，有的人更了解議題的實際情境。這些差異不一定是阻力，反而是共同專案值得存在的原因。</p><p>我想把這種合作方式帶回平常的學習：不急著證明自己知道多少，而是先確認大家正在解決同一個問題。</p><h2 id="留下紀錄，也留下下一次合作的可能"><a href="#留下紀錄，也留下下一次合作的可能" class="headerlink" title="留下紀錄，也留下下一次合作的可能"></a>留下紀錄，也留下下一次合作的可能</h2><p>FtO 對我而言，不是一張可以列上排名的證明，而是一次進入跨國技術社群的經驗。</p><p>回來後整理這篇文章，也是想把「參加過」往前推一步：我加入了什麼、當時討論了哪些問題，以及哪些觀點值得留在之後的作品裡。</p><p>很好的回憶，也期待下一次再見。希望到時候不只帶著新的作品，也能帶著這段合作經驗繼續往前。</p><hr><p>活動背景：<a href="https://fto.asia/">Facing the Ocean</a> · <a href="https://kr.linkedin.com/posts/rheemakim01_fto-seoul-2026-registration-activity-7497277319569428481-pg1Y">Code for Korea 的首爾活動介紹</a></p><p>本文於 2026 年 10 月回顧整理，個人經驗依既有活動與開發討論紀錄撰寫；FtO 為非競賽活動，本文不宣稱獲獎或獨立完成整個專案。</p>]]></content>
    
    
    <summary type="html">九月在首爾參加 Facing the Ocean，加入 rep0rter 專案，也認識不同國家與背景的夥伴。這不是得獎故事，而是一次跨國開源協作的紀錄。</summary>
    
    
    
    <category term="學習紀錄" scheme="https://blog.boycelab.com/categories/%E5%AD%B8%E7%BF%92%E7%B4%80%E9%8C%84/"/>
    
    
    <category term="FtO" scheme="https://blog.boycelab.com/tags/FtO/"/>
    
    <category term="開源" scheme="https://blog.boycelab.com/tags/%E9%96%8B%E6%BA%90/"/>
    
    <category term="Civic Tech" scheme="https://blog.boycelab.com/tags/Civic-Tech/"/>
    
    <category term="國際交流" scheme="https://blog.boycelab.com/tags/%E5%9C%8B%E9%9A%9B%E4%BA%A4%E6%B5%81/"/>
    
  </entry>
  
  <entry>
    <title>青年百億加州見習：把 AI、永續與英文協作放在同一張桌上</title>
    <link href="https://blog.boycelab.com/2026/10/05/california-youth-pathfinders-revalue-ai-2026/"/>
    <id>https://blog.boycelab.com/2026/10/05/california-youth-pathfinders-revalue-ai-2026/</id>
    <published>2026-10-05T02:10:00.000Z</published>
    <updated>2026-10-05T02:10:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>今年七月，我透過青年百億海外圓夢基金計畫前往加州見習。這篇把課程、ReValue AI 團隊發表，以及返國後的分享整理在一起，而不是按每天的行程寫成旅遊紀錄。</p><p>這段學習最值得留下的，是技術不再只出現在程式編輯器裡：我們需要把 AI、永續議題與英文表達放在同一張桌上，讓不同背景的人都能理解提案。</p><h2 id="學習場域改變，問題也會改變"><a href="#學習場域改變，問題也會改變" class="headerlink" title="學習場域改變，問題也會改變"></a>學習場域改變，問題也會改變</h2><p>這次見習包含 UC Davis、California State University, Monterey Bay，以及矽谷的學習與探索。課程涵蓋永續發展、AI 工具應用與領導力，讓我從原本比較熟悉的技術視角，接觸更多與生活和社會有關的問題。</p><p>「永續」很容易被當成一個口號。但是當它和食物浪費、物品再利用或循環經濟連在一起，問題就具體了：哪些資源正在被浪費？人們為什麼不選擇再利用？我們提出的方法，會不會增加另一種負擔？</p><p>這也改變了討論 AI 的方式。不是先問「哪裡可以放 AI」，而是先說清楚原本的困難，再判斷 AI 是否真的能幫忙。</p><h2 id="ReValue-AI：讓二手物品有機會再次被使用"><a href="#ReValue-AI：讓二手物品有機會再次被使用" class="headerlink" title="ReValue AI：讓二手物品有機會再次被使用"></a>ReValue AI：讓二手物品有機會再次被使用</h2><p>我們的團隊提案叫做 <strong>ReValue AI</strong>，把 AI 與物品再利用連在一起。</p><p>概念是：協助使用者辨識物品、理解可能的價值，再提供重新使用或轉售的建議。它想處理的不是一個炫技問題，而是人們面對閒置物品時常有的困惑——不知道它還能做什麼，也不知道怎麼開始處理。</p><p>我參與的部分包括 AI 與技術流程、風險分析、Responsible AI，以及英文簡報與排練。要把提案說清楚，就不能只把模型名稱或流程圖放上去，還必須說明使用者會經歷什麼，以及每一步需要哪些資訊。</p><p>這裡也要區分成果的性質：<strong>ReValue AI 是這段見習中整理的團隊概念與成果發表，不代表已經上線營運、完成市場驗證的商業產品。</strong></p><h2 id="AI-的限制，也應該是提案的一部分"><a href="#AI-的限制，也應該是提案的一部分" class="headerlink" title="AI 的限制，也應該是提案的一部分"></a>AI 的限制，也應該是提案的一部分</h2><p>如果 AI 提供估價或辨識建議，使用者可能會把它當成答案。但物品的狀況、地區、交易市場與圖片品質，都會影響判斷。</p><p>因此，一個負責任的提案不能只說「AI 可以幫你估價」，也要想到：</p><ul><li>建議值不等於保證成交的價格。</li><li>輸入資訊不足時，應該提醒使用者，而不是假裝很確定。</li><li>判斷需要保留人工確認的空間。</li><li>蒐集資料時，必須留意隱私與用途。</li></ul><p>這些是提案設計需要面對的問題，不是已經完成的功能宣告。對我而言，把限制說出來，不是在削弱技術，而是在讓它更可信。</p><h2 id="用英文合作，不只是把中文換成英文"><a href="#用英文合作，不只是把中文換成英文" class="headerlink" title="用英文合作，不只是把中文換成英文"></a>用英文合作，不只是把中文換成英文</h2><p>成果發表的主題是 <strong>Learning in California, Leading in Taiwan</strong>。</p><p>準備英文簡報的過程，包含內容安排、句子的簡化、銜接與團隊排練。這和自己完成一份技術筆記不同：台上的每一段，都必須讓聽眾接得上，也要讓團隊成員知道彼此怎麼銜接。</p><p>我也更清楚地看到，「理解技術」與「讓別人理解技術」需要一起練習。把話說得清楚，不代表內容變得不專業；相反地，它要求我先知道自己到底想表達什麼。</p><h2 id="回來之後，讓經驗繼續流動"><a href="#回來之後，讓經驗繼續流動" class="headerlink" title="回來之後，讓經驗繼續流動"></a>回來之後，讓經驗繼續流動</h2><p>返國後，我也把申請、海外學習與自主學習的經驗整理成分享。對我而言，機會的價值不只在於自己去了哪裡，也在於能不能讓下一個正在找資源的人，更容易知道有哪些可能。</p><p>我希望 BoyceLab 同樣扮演這個角色：不是只展示完成的成果，而是把背景、方法與限制寫出來，讓讀者有機會接著思考。</p><p>這次加州見習留下的，不只是一段海外經驗，而是一個持續提醒：<strong>先理解問題，再選擇技術；做好作品，也要讓人看得懂。</strong></p><hr><p>活動背景：<a href="https://www.linkedin.com/posts/foundation-for-scholarly-exchange_fulbrighttaiwan-taiwanglobalpathfinders-activity-7504101806835380224-ceCr">Fulbright Taiwan 的加州見習介紹</a> · <a href="https://twpathfinder.yda.gov.tw/">青年百億海外圓夢基金計畫</a></p><p>本文於 2026 年 10 月回顧整理；課程背景參考主辦單位公開資訊，個人參與內容依既有見習與成果發表紀錄整理。</p>]]></content>
    
    
    <summary type="html">整理青年百億加州見習與 ReValue AI 團隊發表：從永續議題出發，練習解釋 AI 能做什麼、不能做什麼，再把經驗帶回來分享。</summary>
    
    
    
    <category term="學習紀錄" scheme="https://blog.boycelab.com/categories/%E5%AD%B8%E7%BF%92%E7%B4%80%E9%8C%84/"/>
    
    
    <category term="AI" scheme="https://blog.boycelab.com/tags/AI/"/>
    
    <category term="青年百億" scheme="https://blog.boycelab.com/tags/%E9%9D%92%E5%B9%B4%E7%99%BE%E5%84%84/"/>
    
    <category term="海外見習" scheme="https://blog.boycelab.com/tags/%E6%B5%B7%E5%A4%96%E8%A6%8B%E7%BF%92/"/>
    
    <category term="永續" scheme="https://blog.boycelab.com/tags/%E6%B0%B8%E7%BA%8C/"/>
    
  </entry>
  
  <entry>
    <title>從科展到 AI 安全：不只做出作品，也學會把問題問清楚</title>
    <link href="https://blog.boycelab.com/2026/10/05/science-fair-ai-security-2026/"/>
    <id>https://blog.boycelab.com/2026/10/05/science-fair-ai-security-2026/</id>
    <published>2026-10-05T02:00:00.000Z</published>
    <updated>2026-10-05T02:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>這篇不是把獎項再列一次，而是整理一路做科展時，我的問題意識怎麼改變。</p><p>從第 65 屆分區科展的智慧回收系統，到第 66 屆第 6 分區中小學科展特優，以及第 25 屆旺宏科學獎佳作，我想留下的不只是結果。更重要的是：每次整理作品、準備說明與重新檢查方法，都讓我看到自己還能做得更好的地方。</p><blockquote><p>還在不斷精進自己，每天都不一樣。</p></blockquote><p>這也是我最近整理這段經歷時，最想表達的一句話。</p><h2 id="第一段：讓-AI-從螢幕走進真實環境"><a href="#第一段：讓-AI-從螢幕走進真實環境" class="headerlink" title="第一段：讓 AI 從螢幕走進真實環境"></a>第一段：讓 AI 從螢幕走進真實環境</h2><p>智慧回收系統是我早期將 AI 與硬體串起來的研究。影像辨識只是其中一環，後面還有 Raspberry Pi、感測器、馬達，以及整個回收流程。</p><p>如果只看程式畫面，很容易覺得「模型辨識出來了，作品就完成了」。但是把它放進實際裝置後，問題會變得不同：圖片輸入是否穩定？辨識結果如何交給控制程式？裝置遇到錯誤時要如何處理？</p><p>這讓我開始理解，<strong>模型、程式與硬體要放在同一個流程裡看</strong>。其中一段出了問題，使用者看到的就是整個系統沒有正常工作，而不是「這一小段其實很成功」。</p><h2 id="第二段：從使用-AI，走向研究-AI-的安全"><a href="#第二段：從使用-AI，走向研究-AI-的安全" class="headerlink" title="第二段：從使用 AI，走向研究 AI 的安全"></a>第二段：從使用 AI，走向研究 AI 的安全</h2><p>後來，我把研究方向延伸到 RAG 與文件注入。</p><p>先用比較白話的方式說明：<strong>RAG 就是讓 AI 回答前，先查閱一份外部資料庫或文件</strong>。這個方法很實用，例如讓助理依照公司文件回答問題，而不是只依賴模型記得的內容。</p><p>但也因此多了一個問題：如果被查到的文件，混入了不應該被當成指令的文字，AI 會不會跟著它走？</p><p>這就是文件注入研究想追問的事情。外部內容應該是「可供參考的資料」，不應該因為出現在檢索結果裡，就取得指揮系統的權限。</p><p>研究裡涉及的切片、重疊與檢索參數，看起來像很細的技術設定，但它們其實都在影響同一件事：<strong>模型最後會看到哪些內容，以及它如何使用這些內容</strong>。</p><h2 id="科展讓我練習的不只是上台"><a href="#科展讓我練習的不只是上台" class="headerlink" title="科展讓我練習的不只是上台"></a>科展讓我練習的不只是上台</h2><p>作品可以執行，和研究可以被理解，是兩件不同的事。</p><p>整理報告時，我必須把問題、方法與觀察分開，不能只把畫面或功能一口氣堆上去。準備說明時，也要練習先交代問題的重要性，再介紹專有名詞。</p><p>我現在也把這個要求帶回網站：如果一個頁面看起來很專業，卻讓人不知道可以做什麼，那還不算完成；研究也是一樣，如果只能列出很多技術，卻說不清楚它們在回答什麼問題，就還需要整理。</p><p>對我而言，下一步不是把所有事情說得更大，而是把每個結論的範圍、限制與驗證方法說得更清楚。</p><h2 id="把研究帶進大學的學習"><a href="#把研究帶進大學的學習" class="headerlink" title="把研究帶進大學的學習"></a>把研究帶進大學的學習</h2><p>現在進入中興大學應用數學系，我希望把過去實作的經驗，和數學、資料分析與計算方法連起來。</p><p>以前遇到問題時，我常先想「要用什麼工具做」。接下來，我也想更有能力判斷資料、設計比較方式，理解結果背後的條件，而不只是讓程式跑出一個數字。</p><p>這條路還沒有走完。科展和競賽是階段性的紀錄，持續提問、修正和公開整理，才是我想長期留下的部分。</p><hr><p>延伸閱讀：<a href="/tags/%E8%B3%87%E5%AE%89/">我的資安學習筆記</a> · <a href="/about/">關於我的學習路線</a></p><p>本文於 2026 年 10 月整理發布；活動成果依既有個人紀錄整理，不把分區成果或代表資格當作全國賽得獎。</p>]]></content>
    
    
    <summary type="html">從智慧回收系統到 RAG 文件注入研究，整理科展與旺宏科學獎這段路，還有我如何從「做出來」走向「說清楚、驗證清楚」。</summary>
    
    
    
    <category term="學習紀錄" scheme="https://blog.boycelab.com/categories/%E5%AD%B8%E7%BF%92%E7%B4%80%E9%8C%84/"/>
    
    
    <category term="資安" scheme="https://blog.boycelab.com/tags/%E8%B3%87%E5%AE%89/"/>
    
    <category term="科展" scheme="https://blog.boycelab.com/tags/%E7%A7%91%E5%B1%95/"/>
    
    <category term="AI" scheme="https://blog.boycelab.com/tags/AI/"/>
    
    <category term="研究" scheme="https://blog.boycelab.com/tags/%E7%A0%94%E7%A9%B6/"/>
    
  </entry>
  
  <entry>
    <title>SITCON 參與心得｜從屏東出發，看見語言模型的另一面</title>
    <link href="https://blog.boycelab.com/2026/04/21/sitcon2026/"/>
    <id>https://blog.boycelab.com/2026/04/21/sitcon2026/</id>
    <published>2026-04-20T16:00:00.000Z</published>
    <updated>2026-08-24T17:00:31.157Z</updated>
    
    <content type="html"><![CDATA[<hr><img src="/images/posts/2026-4-21/20260328_083421.jpg" style="max-width: 50%;"><p>這次參加 SITCON，對我來說不只是去聽幾場技術議程而已，更像是一次把自己從日常學習環境中抽離、重新看見資訊領域樣貌的機會。</p><p>平常待在屏東，能接觸到的活動、社群與現場交流機會，本來就和北部有明顯落差。很多時候，網路上的文章、影片、論文當然也能學習，但那種「人在現場」的感受其實完全不同。親自走進會場、聽講者分享、看到台下聽眾的反應、感受到整個社群對技術議題的熱情，這些東西不是只靠看轉播就能完全取代的。</p><p>所以這次能夠參與 SITCON，我最想先說的，其實是感謝。</p><p>特別感謝 SITCON 提供遠道而來的補助，讓像我這樣從屏東出發的學生，也有機會到現場接觸這麼高品質的議程內容。這不只是減輕交通與參與上的負擔而已，更重要的是，它真的讓不同地區的學生有機會站在同一個場域裡學習、思考與交流。對我來說，這份補助不只是資源上的幫助，而是一種很實際的支持：讓我知道即使人在南部，也一樣可以被接住，也一樣有機會靠近這些前沿的技術討論。</p><p>也很謝謝主辦方籌備這場活動。從議程安排、主題深度，到整體社群氛圍，都能感受到這不是一場只是把人聚在一起的活動，而是一個真的希望讓大家交流、思辨與成長的技術場域。能夠參與其中，我很珍惜。</p><p>而在這次的所有議程中，我最有感的兩場，分別是：</p><ol><li><a href="https://www.youtube.com/watch?v=GHC2oRj7TS4">透視 AI 抓內鬼：以注意力防範提示注入</a> </li><li><a href="https://www.youtube.com/watch?v=1jAxdU12vYE">語言模型的認知、認同與對齊</a></li></ol><p>這兩場表面上切入角度不同，一場偏 AI 安全與攻擊防禦，一場偏模型行為、價值塑形與對齊問題；但聽完之後，我反而覺得它們都指向同一件事：</p><blockquote><p><strong>語言模型不是只要會用就好，更重要的是要知道它為什麼會這樣運作。</strong></p></blockquote><hr><h2 id="一、整體參與心得：SITCON-讓我重新感受到「資訊社群」的力量"><a href="#一、整體參與心得：SITCON-讓我重新感受到「資訊社群」的力量" class="headerlink" title="一、整體參與心得：SITCON 讓我重新感受到「資訊社群」的力量"></a>一、整體參與心得：SITCON 讓我重新感受到「資訊社群」的力量</h2><p>這次參加 SITCON，我最大的感受之一，是我重新感受到資訊社群的密度與能量。</p><p>平常自己學技術時，很容易把注意力放在「我要學哪個工具、我要做哪個專案、我要把哪篇論文看懂」。但到了現場之後，我反而更明確感受到，技術不是只有工具本身，還包含很多更深的東西：</p><ul><li>有人在思考模型安全怎麼做得更扎實</li><li>有人在思考語言模型的偏見與對齊問題</li><li>有人在做系統、做研究、做社群</li><li>也有人在試著把困難的東西講清楚，讓更多人能進來理解</li></ul><p>這種感覺很重要，因為它會提醒我：資訊不是只是在電腦前面寫程式而已，而是一整個持續發展、彼此交流、互相推進的領域。</p><p>而且我覺得 SITCON 很好的地方，是它不是只有很表面的「技術新知展示」，而是真的有不少議程在處理本質問題。像這次我最喜歡的兩場，就都不是停留在「這個模型很好用」「這個方法很強」這種層次，而是會進一步追問：</p><ul><li>模型到底為什麼會被攻擊？</li><li>模型對世界的理解是怎麼形成的？</li><li>所謂的對齊，到底是在對齊什麼？</li><li>模型的立場，是它真的有認同，還是只是表面表現？</li></ul><p>這些問題不是那種聽完馬上就能寫成程式的內容，但它們反而是更重要的基礎。因為如果連問題本身都沒有想清楚，那之後做出來的系統，很可能只是能跑，卻不一定真的可靠。</p><p>這次參與下來，我也更確定自己喜歡的方向，不只是單純把系統做出來，而是那種會去追問底層原因、會想理解模型行為、會想把安全性與實務場景一起考慮進去的題目。這也是這次 SITCON 對我最大的價值之一。</p><p>另外一個讓我印象很深的，是這次活動中的「大地遊戲」。這個設計其實不只是好玩而已，它很有效地讓大家在活動一開始就有機會互動、破冰，讓原本彼此不認識的人可以自然地開始交流。我覺得這點很關鍵，因為很多技術活動的問題是大家都各自聽講，很難真正建立連結，但這個設計讓整體社群感更強。</p><p>除此之外，會場外也有很多攤位可以逛，不只是拿贈品（雖然真的拿了不少），更重要的是能直接和不同團隊、社群或公司的人交流。我自己也有趁這個機會去問了一些關於未來方向與就職的問題，某種程度上也幫我更釐清自己之後想走的路。</p><p>整體來說，這次 SITCON 對我來說，不只是學到技術內容，更像是讓我更確定「我要成為什麼樣的工程師，以及我要在這個領域做什麼」。</p><hr><h2 id="二、議程心得（一）：透視-AI-抓內鬼：以注意力防範提示注入"><a href="#二、議程心得（一）：透視-AI-抓內鬼：以注意力防範提示注入" class="headerlink" title="二、議程心得（一）：透視 AI 抓內鬼：以注意力防範提示注入"></a>二、議程心得（一）：透視 AI 抓內鬼：以注意力防範提示注入</h2><p>這一場是我非常有感的一場，因為它談的問題本身就很實際：<strong>Prompt Injection 到底要怎麼防？</strong></p><p>現在大家在談 AI 應用時，常常會把重點放在模型有多強、功能有多少，但只要系統真的開始接外部資料、接工具、接網頁、接信件，Prompt Injection 這種問題就不是「會不會發生」，而是「什麼時候會出事」。</p><p>而這場議程厲害的地方，是它不是只停在外部防禦，而是往模型裡面看。</p><h2 id="1-提示注入的本質，不只是惡意字串，而是讓模型分心"><a href="#1-提示注入的本質，不只是惡意字串，而是讓模型分心" class="headerlink" title="1. 提示注入的本質，不只是惡意字串，而是讓模型分心"></a>1. 提示注入的本質，不只是惡意字串，而是讓模型分心</h2><p>這場最讓我記住的一個觀點，就是：</p><blockquote><p><strong>提示注入的本質，可能是讓模型不再專注於原本應該遵守的系統指令。</strong></p></blockquote><p>這個說法我覺得很強，因為它把問題從「外部輸入長得像不像攻擊」拉到「模型內部到底發生了什麼」。</p><p>很多防禦方法其實都像是在外面貼膠帶：加規則、加過濾器、加黑名單、加額外 classifier。這些方法不是沒用，但問題是攻擊一直在變，如果永遠只是補外面，常常就只是追著攻擊跑。</p><p>這場議程給我的啟發是：如果注入攻擊真的造成的是注意力偏移，那就應該從模型的注意力機制本身下手。這個視角讓我覺得很新鮮，也很合理。</p><h2 id="2-用注意力找「分心」的頭，這個想法很漂亮"><a href="#2-用注意力找「分心」的頭，這個想法很漂亮" class="headerlink" title="2. 用注意力找「分心」的頭，這個想法很漂亮"></a>2. 用注意力找「分心」的頭，這個想法很漂亮</h2><p>講者提到的方法，核心是先找出那些在攻擊出現時，會明顯對系統指令失去關注的 attention heads，再用這些 heads 的表現去計算 Focus Score。當分數低到某個程度，就代表模型可能已經被帶偏，這時候就拒絕回答或阻擋輸入。</p><p>我喜歡這種方法，不是因為它聽起來高級，而是因為它有一種「從現象回到機制」的感覺。</p><p>它不是只說「這段文字像攻擊」，而是去問：</p><ul><li>攻擊進來之後，模型還有沒有在看系統指令？</li><li>哪些頭本來負責規則相關的關注？</li><li>它們是不是在攻擊時開始失去功能？</li></ul><p>這種做法比單純做字串比對更像是在理解模型，而不是只是把模型當黑盒子。</p><h2 id="3-真正有價值的地方，是講者沒有把方法講成萬能"><a href="#3-真正有價值的地方，是講者沒有把方法講成萬能" class="headerlink" title="3. 真正有價值的地方，是講者沒有把方法講成萬能"></a>3. 真正有價值的地方，是講者沒有把方法講成萬能</h2><p>這場另一個我很喜歡的地方，是它沒有把這種方法包裝成「完美解法」。</p><p>講者後面有繼續做很多壓力測試，例如：</p><ul><li>如果惡意內容藏在長文本中間怎麼辦？</li><li>如果系統指令和攻擊語言不同怎麼辦？</li><li>如果模型變大或變小，效果是否穩定？</li><li>如果輸入越來越長，固定閾值還合理嗎？</li></ul><p>而結論也不是那種「方法超強完全能擋」，而是更誠實地指出它的邊界：</p><ul><li>長文本會讓模型對固定系統指令的關注自然下降</li><li>小模型在長文本下效果明顯更差</li><li>跨語言情境仍然可能讓判斷難度上升</li><li>真實部署時需要考慮動態閾值，而不能只用固定 cutoff</li></ul><p>這一段我特別有感，因為這才是真正接近研究與實務的樣子。很多東西不是「有用或沒用」二分法，而是它在什麼條件下有效、又會在什麼條件下失效。</p><h2 id="4-我自己的收穫"><a href="#4-我自己的收穫" class="headerlink" title="4. 我自己的收穫"></a>4. 我自己的收穫</h2><p>這場議程對我的影響，不只是學到一種 prompt injection 防禦方法，而是讓我更確定一件事：</p><blockquote><p><strong>AI 安全不能只停在表面規則，真正有價值的防禦，應該盡可能往模型機制本身靠近。</strong></p></blockquote><p>而且我覺得這種思維不只適用在提示注入上，放到我自己關注的 RAG、文件內容污染、模型被上下文誤導等問題，其實也很有共鳴。因為很多時候，真正值得研究的不是「模型有沒有出錯」，而是「模型是怎麼一步一步被帶到錯的地方去的」。</p><hr><h2 id="三、議程心得（二）：語言模型的認知、認同與對齊"><a href="#三、議程心得（二）：語言模型的認知、認同與對齊" class="headerlink" title="三、議程心得（二）：語言模型的認知、認同與對齊"></a>三、議程心得（二）：語言模型的認知、認同與對齊</h2><p>如果說第一場是在談模型如何被攻擊，那第二場更像是在談：</p><blockquote><p><strong>模型平常看起來那麼會回答，它到底是真的理解，還是只是被訓練成看起來很像理解？</strong></p></blockquote><p>這場我很喜歡，因為它不是從應用層面切入，而是直接拆解語言模型的行為基礎。整場圍繞三個問題展開：</p><ol><li>模型怎麼形成對世界的認知？</li><li>訓練資料真的能正確呈現世界嗎？</li><li>模型真的有穩定的認同或立場嗎？</li></ol><p>我覺得這三題問得非常準，因為現在很多人用模型時，太容易直接把輸出當成理解、把回覆當成立場、把順從當成對齊。</p><h2 id="1-模型的「知道」，和人的知道不是同一件事"><a href="#1-模型的「知道」，和人的知道不是同一件事" class="headerlink" title="1. 模型的「知道」，和人的知道不是同一件事"></a>1. 模型的「知道」，和人的知道不是同一件事</h2><p>這場議程重新梳理了模型在預訓練與後訓練中的角色分工。預訓練讓模型透過大量文字去學習世界中各種統計規律與抽象概念，而後訓練則更多是在調整它怎麼回應人類、怎麼展現出一種「好用」的樣子。</p><p>這裡最值得記住的一點是：</p><blockquote><p><strong>模型在預訓練中做的是下一個 token 預測，但在這個過程裡，它會被迫壓縮、抽取、內化世界中的某些規律。</strong></p></blockquote><p>所以它看起來像知道很多事，但這種「知道」和人類從經驗、理解、脈絡中形成的知識，並不相同。</p><p>這也讓我反思，平常在和模型互動時，很容易不自覺把它擬人化，覺得它「懂」「認同」「站在某一邊」。但這場提醒我，這些詞都需要更小心地使用。</p><h2 id="2-資料不是世界，而是被偏差、被過濾過的世界"><a href="#2-資料不是世界，而是被偏差、被過濾過的世界" class="headerlink" title="2. 資料不是世界，而是被偏差、被過濾過的世界"></a>2. 資料不是世界，而是被偏差、被過濾過的世界</h2><p>這場另一個我覺得很重要的部分，是對資料偏差的討論。</p><p>講者不是只講「資料有偏見」這種很常見的話，而是具體談到兩個層次：</p><p>第一個層次，是資料本身的代表性不足。像繁體中文、台灣語境相關的內容，在大型模型訓練資料裡的占比本來就偏低，這會直接影響模型對在地文化、語言使用與概念脈絡的掌握。</p><p>第二個層次，是資料清洗流程本身也會加劇問題。當資料集用某些規則去排除不符合標準的內容時，少數族群、非主流語言風格、特殊文化用法，反而更容易被刪掉。</p><p>我覺得這件事很重要，因為它不是單純的準確率問題，而是更深的代表性問題。也就是說，模型後來長成什麼樣子，不只是由資料量決定，而是由「哪些東西被留下來，哪些東西被過濾掉」共同決定的。</p><p>這對台灣使用者尤其有感。因為很多模型雖然支援中文，但它不一定真的理解台灣脈絡中的中文。這兩者差很多。</p><h2 id="3-模型的「立場」，很可能只是學到某種回應風格"><a href="#3-模型的「立場」，很可能只是學到某種回應風格" class="headerlink" title="3. 模型的「立場」，很可能只是學到某種回應風格"></a>3. 模型的「立場」，很可能只是學到某種回應風格</h2><p>後半段談到「認同」與「對齊」時，我覺得是整場最有後勁的部分。</p><p>平常大家很常說某個模型比較保守、比較中立、比較傾向某個價值，但這場議程提醒了一件事：模型表現出來的立場，不一定等於它像人一樣真的形成穩定價值觀。</p><p>它更可能是：</p><ul><li>受訓練資料影響後學到某種回答傾向</li><li>在特定 prompt 和上下文下抽出某種最適合的說法</li><li>在不同情境中展現不同的表面立場</li></ul><p>換句話說，它表現得像有立場，不代表它真的像人一樣有穩定的信念結構。</p><p>我覺得這點很值得反覆想。因為如果我們一開始就把模型當成有穩定人格、有一致價值、有固定認同的主體，那很多 alignment 的討論其實很容易偏掉。</p><h2 id="4-我自己的收穫-1"><a href="#4-我自己的收穫-1" class="headerlink" title="4. 我自己的收穫"></a>4. 我自己的收穫</h2><p>這場議程對我最大的提醒，是：</p><blockquote><p><strong>在評估語言模型時，不能只看它回答了什麼，還要追問它為什麼會回答成這樣。</strong></p></blockquote><p>這句話聽起來很簡單，但其實會影響很多事。</p><p>像是：</p><ul><li>模型輸出的偏見，是來自資料分布還是後訓練？</li><li>模型看起來很中立，是真的中立，還是只是學會迴避？</li><li>模型表現出某種價值觀，是內在一致，還是被 prompt 拉出來的暫時人格？</li></ul><p>這些問題沒有簡單答案，但正因為沒有簡單答案，才更值得研究。</p><hr><h2 id="四、總結：這次-SITCON-讓我更想理解模型，而不只是使用模型"><a href="#四、總結：這次-SITCON-讓我更想理解模型，而不只是使用模型" class="headerlink" title="四、總結：這次 SITCON 讓我更想理解模型，而不只是使用模型"></a>四、總結：這次 SITCON 讓我更想理解模型，而不只是使用模型</h2><p>回頭看這次 SITCON，我覺得自己最大的收穫，不是哪一個新名詞，也不是哪一篇論文，而是一種看待語言模型的方式被重新整理了。</p><p>第一場讓我看到，模型不是只要外面包幾層規則就安全，它的內部機制本身就值得被打開來看。</p><p>第二場則讓我意識到，模型看起來會回答問題，不代表它真的像人一樣理解；模型看起來有立場，也不代表它真的像人一樣擁有穩定認同。</p><p>而把這兩場放在一起看，我最後得到的一個很清楚的感想是：</p><blockquote><p><strong>語言模型不是一個只要會下 prompt 就能完全掌握的工具，它是一個會被資料塑形、會被攻擊牽動、會被訓練方式改變行為的複雜系統。</strong></p></blockquote><p>這也讓我更確定，之後不管是在做研究、做專題、做應用，自己不該只滿足於「它能不能跑」，而是要更常去追問：</p><ul><li>它為什麼這樣回答？</li><li>它在什麼條件下會失效？</li><li>它表面的表現，背後到底是能力還是偏差？</li></ul><p>最後，再次感謝 SITCON 與主辦團隊，也感謝提供遠道補助，讓我能從屏東到現場參與這場活動。對我來說，這不只是一趟聽議程的行程，而是一個讓我更確定自己想往哪裡走的經驗。</p><p>如果要用一句話總結這次心得，我會這樣寫：</p><blockquote><p><strong>這次 SITCON 讓我學到的，不只是怎麼使用語言模型，而是怎麼更認真地理解語言模型。</strong></p></blockquote>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;hr&gt;
&lt;img src=&quot;/images/posts/2026-4-21/20260328_083421.jpg&quot; style=&quot;max-width: 50%;&quot;&gt;

&lt;p&gt;這次參加</summary>
        
      
    
    
    
    <category term="心得" scheme="https://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/"/>
    
    <category term="技術" scheme="https://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/%E6%8A%80%E8%A1%93/"/>
    
    <category term="AI" scheme="https://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/%E6%8A%80%E8%A1%93/AI/"/>
    
    <category term="LLM" scheme="https://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/%E6%8A%80%E8%A1%93/AI/LLM/"/>
    
    <category term="資安" scheme="https://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/%E6%8A%80%E8%A1%93/AI/LLM/%E8%B3%87%E5%AE%89/"/>
    
    <category term="Prompt Injection" scheme="https://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/%E6%8A%80%E8%A1%93/AI/LLM/%E8%B3%87%E5%AE%89/Prompt-Injection/"/>
    
    <category term="Alignment" scheme="https://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/%E6%8A%80%E8%A1%93/AI/LLM/%E8%B3%87%E5%AE%89/Prompt-Injection/Alignment/"/>
    
    
    <category term="SITCON2026" scheme="https://blog.boycelab.com/tags/SITCON2026/"/>
    
  </entry>
  
  <entry>
    <title>演算法筆記：八大排序演算法</title>
    <link href="https://blog.boycelab.com/2026/03/05/sorting-algorithms/"/>
    <id>https://blog.boycelab.com/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://blog.boycelab.com/categories/%E6%BC%94%E7%AE%97%E6%B3%95/"/>
    
    <category term="Algorithm" scheme="https://blog.boycelab.com/categories/%E6%BC%94%E7%AE%97%E6%B3%95/Algorithm/"/>
    
    <category term="C++" scheme="https://blog.boycelab.com/categories/%E6%BC%94%E7%AE%97%E6%B3%95/Algorithm/C/"/>
    
    <category term="競程" scheme="https://blog.boycelab.com/categories/%E6%BC%94%E7%AE%97%E6%B3%95/Algorithm/C/%E7%AB%B6%E7%A8%8B/"/>
    
    <category term="教學" scheme="https://blog.boycelab.com/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://blog.boycelab.com/tags/%E6%BC%94%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>115 特殊選才心得｜資工特選的現實與轉彎</title>
    <link href="https://blog.boycelab.com/2025/12/24/115-special/"/>
    <id>https://blog.boycelab.com/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://blog.boycelab.com/categories/%E5%BF%83%E5%BE%97/"/>
    
    
    <category term="心得" scheme="https://blog.boycelab.com/tags/%E5%BF%83%E5%BE%97/"/>
    
  </entry>
  
  <entry>
    <title>2025 AIS3 Junior Writeup｜從基礎 Web 到 XSS 與後端漏洞</title>
    <link href="https://blog.boycelab.com/2025/08/13/AIS3-Full-Security-Record/"/>
    <id>https://blog.boycelab.com/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://blog.boycelab.com/categories/Writeup/"/>
    
    <category term="2025 AIS3 Junior" scheme="https://blog.boycelab.com/categories/Writeup/2025-AIS3-Junior/"/>
    
    
  </entry>
  
  <entry>
    <title>Filename Confusion | Writeup</title>
    <link href="https://blog.boycelab.com/2025/08/13/250813-filename-confusion/"/>
    <id>https://blog.boycelab.com/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://blog.boycelab.com/categories/%E8%B3%87%E5%AE%89/"/>
    
    <category term="Writeup" scheme="https://blog.boycelab.com/categories/%E8%B3%87%E5%AE%89/Writeup/"/>
    
    
    <category term="資安" scheme="https://blog.boycelab.com/tags/%E8%B3%87%E5%AE%89/"/>
    
  </entry>
  
  <entry>
    <title>拍貼機系統 P!X 印一下</title>
    <link href="https://blog.boycelab.com/2025/07/26/Photobooth_Post/"/>
    <id>https://blog.boycelab.com/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://blog.boycelab.com/categories/%E5%B0%88%E6%A1%88/"/>
    
    <category term="拍貼" scheme="https://blog.boycelab.com/categories/%E5%B0%88%E6%A1%88/%E6%8B%8D%E8%B2%BC/"/>
    
    <category term="P!X" scheme="https://blog.boycelab.com/categories/%E5%B0%88%E6%A1%88/%E6%8B%8D%E8%B2%BC/P-X/"/>
    
    
    <category term="專案" scheme="https://blog.boycelab.com/tags/%E5%B0%88%E6%A1%88/"/>
    
  </entry>
  
</feed>
