貪婪演算法
每一步都挑眼前最好的,從不回頭。有些問題這樣就是最佳解(區間排程),有些問題這樣會錯(某些面額的找零),差別在能不能證明。
問題
1/11
排進去了重疊,跳過先前跳過的虛線:已排會議的結束時間
一間會議室、很多場會議,要排進最多場。先依結束時間排序,最早結束的在前。
同一批會議,換別的規則
- 最早結束的先排6 場B C D E F G
- 最早開始的先排3 場A F G
- 最短的先排4 場H I F G
亮起來的是這一步執行的程式碼
type Meeting = [number, number]; // [start, end) function maxMeetings(meetings: Meeting[]): Meeting[] { const sorted = [...meetings].sort((a, b) => a[1] - b[1] || a[0] - b[0]); const chosen: Meeting[] = []; let lastEnd = -Infinity; for (const [start, end] of sorted) { if (start < lastEnd) continue; chosen.push([start, end]); lastEnd = end; } return chosen;} function greedyChange(coins: number[], amount: number): number[] | null { const used: number[] = []; for (const coin of [...coins].sort((a, b) => b - a)) { while (amount >= coin) { used.push(coin); amount -= coin; } } return amount === 0 ? used : null;}模型假設與範圍
- Big O 與實測步數以頁面列出的基本操作為單位;視覺化的快照、畫圖、程式高亮和輸出成本另計,不是執行時間 benchmark。
什麼時候用
- 能證明「現在選最好的,不會害到後面」的時候:區間排程、霍夫曼編碼、最小生成樹、Dijkstra 都是貪婪,而且都有證明。
- 要一個夠好、而且非常快的近似解時:很多 NP 困難(NP-hard;NP 指 Nondeterministic Polynomial time)問題(背包、集合覆蓋)的實務做法就是貪婪。
- 證明不了、又需要保證最佳時,改用動態規劃或搜尋。
和其他主題的關係
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| 會議排程(最早結束) 排序之後只走一遍 | O(n log n) | O(n log n) |
| 貪婪找零 k 是硬幣種類、c 是用了幾枚;只對某些幣值組合是最佳解 | O(k + c) | O(k + amount) |
| 動態規劃找零(保證最少) | O(k · amount) | O(k · amount) |
空間:O(n)
Big O 實測:n 變大時步數怎麼長
數的是:比較次數:排序+走一遍(n 場隨機會議)
| Big O | n = 1,000 | n = 10,000 | n = 100,000 | 成長倍數:實測(理論) | |
|---|---|---|---|---|---|
| 最早結束的先排 | O(n log n) | 9,736 | 133,736 | 1,666,641 | ×171 (×167) |
和其他做法比
| 平均排進幾場 | 達到最多場的比例 | |
|---|---|---|
| 最早結束的先排 | 5.08 | 100% |
| 最早開始的先排 | 4.90 | 83% |
| 最短的先排 | 5.02 | 94% |
| 暴力試遍所有組合 | 5.08 | 100% |
300 組隨機產生的 10 場會議(0 到 24 點之間開始、長 1 到 6 小時),每組都用暴力法算出真正的最多場數來對照。只有「最早結束」每一組都是最佳解,這可以證明;另外兩條規則聽起來合理,但常常排少了。
真實世界裡的它
- 會議室、教室、機台的排程。
- 壓縮格式(ZIP、PNG、JPEG)裡的霍夫曼編碼:每次合併出現次數最少的兩個符號。
- 收銀機找零:新台幣和美元的幣值設計讓「先拿最大的」永遠是最少枚數。
取捨與陷阱
- 貪婪看起來對,不代表對:「最早開始」「最短的先排」都很直覺,卻會排少。換一條規則,就要重新證明一次。
- 找零的貪婪只對某些幣值成立:硬幣是 {1, 3, 4} 時,湊 6 元貪婪拿 4+1+1 三枚,最少其實是 3+3 兩枚。
- 區間的端點要說清楚是開是閉:10 點結束和 10 點開始算不算重疊,會直接改變答案。