跳到主要內容

演算法

同一個問題,不同的解題思路

主題 · 貪婪演算法

貪婪演算法

每一步都挑眼前最好的,從不回頭。有些問題這樣就是最佳解(區間排程),有些問題這樣會錯(某些面額的找零),差別在能不能證明。

問題
1/11
02468101214161820B1–4H3–5C4–7D7–10I9–11A0–12E10–13F13–16G16–19
排進去了重疊,跳過先前跳過的虛線:已排會議的結束時間

一間會議室、很多場會議,要排進最多場。先依結束時間排序,最早結束的在前。

同一批會議,換別的規則
  • 最早結束的先排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 On = 1,000n = 10,000n = 100,000成長倍數:實測(理論)
最早結束的先排O(n log n)9,736133,7361,666,641×171 (×167)

和其他做法比

平均排進幾場達到最多場的比例
最早結束的先排5.08100%
最早開始的先排4.9083%
最短的先排5.0294%
暴力試遍所有組合5.08100%

300 組隨機產生的 10 場會議(0 到 24 點之間開始、長 1 到 6 小時),每組都用暴力法算出真正的最多場數來對照。只有「最早結束」每一組都是最佳解,這可以證明;另外兩條規則聽起來合理,但常常排少了。

真實世界裡的它

  • 會議室、教室、機台的排程。
  • 壓縮格式(ZIP、PNG、JPEG)裡的霍夫曼編碼:每次合併出現次數最少的兩個符號。
  • 收銀機找零:新台幣和美元的幣值設計讓「先拿最大的」永遠是最少枚數。

取捨與陷阱

  • 貪婪看起來對,不代表對:「最早開始」「最短的先排」都很直覺,卻會排少。換一條規則,就要重新證明一次。
  • 找零的貪婪只對某些幣值成立:硬幣是 {1, 3, 4} 時,湊 6 元貪婪拿 4+1+1 三枚,最少其實是 3+3 兩枚。
  • 區間的端點要說清楚是開是閉:10 點結束和 10 點開始算不算重疊,會直接改變答案。

LeetCode 練習