Semorphe

優先佇列

永遠先拿最大的那個。 · ⏱ 約 30 分鐘

你會學到三件事

  1. priority_queue 每次拿出來的都是最大的
  2. 它是 O(log n) 進、O(log n) 出、O(1) 看
  3. 貪心:每一步都選當下最好的

開始之前

上一課的兩個容器,順序是「什麼時候進來」決定的。

這一課的容器,順序是值本身決定的。

一、每次拿最大的

priority_queue<int> pq;
pq.push(3);
pq.push(7);
pq.push(1);
cout << pq.top() << endl;      // 7   ← 最大的
pq.pop();
cout << pq.top() << endl;      // 3   ← 剩下裡面最大的

⚠️ stack 一樣用 top(),而意思完全不同: stack 的 top 是「最後放的」,這裡是「最大的」。

二、要最小的

priority_queue<int, vector<int>, greater<int>> pq;

⚠️ 這個寫法很長而且要背——中間那個 vector<int> 不能省, 即使你根本不關心它。

(那是因為第三個參數是比較器,而 C++ 要求前面的都給。)

小的先出 在 Dijkstra(第 12 課)裡是必須的。

三、為什麼不是「排好序的 vector」

插入一個 拿最大的
排好序的 vector O(n)(要搬東西) O(1)
priority_queue O(log n) O(1)

差別在一直有新東西進來的時候。

它裡面是一個叫 heap 的結構——一棵「父親一定比兒子大」的樹。 它不是完全排好序的,而「最大的在最上面」永遠成立。

只需要最大的話,不必把全部排好。 這就是它比排序快的原因。

⚠️ 所以 priority_queue 不能走訪——沒有 begin(), 因為裡面根本不是有序的。

四、貪心:每一步選當下最好的

貪心 = 每一步都選當下看起來最好的,不回頭。

它很快,而它不一定對

題目:用最少的硬幣湊出 15 元,幣值 {1, 5, 10}
貪心:10 + 5 = 兩枚         ✅ 對

同一個做法,幣值 {1, 7, 10},湊 14
貪心:10 + 1 + 1 + 1 + 1 = 五枚
最佳:7 + 7 = 兩枚           🔴 貪心錯了

⚠️ 所以用貪心之前要能說出「為什麼這樣選一定對」。 說不出來就用動態規劃(第 10 課)。

priority_queue 正是貪心的標準工具——「當下最好的」就是 top()

完成的樣子

int main() {
    priority_queue<int> pq;
    pq.push(3);
    pq.push(7);
    pq.push(1);
    cout << pq.top() << endl;
    pq.pop();
    cout << pq.top() << endl;
    return 0;
}

換你了

合併果子:有 n 堆果子,每次合併兩堆的成本是它們的重量和, 求把全部合成一堆的最小總成本。

提示:每次合併最小的兩堆——用小的先出的那種 priority_queue

⚠️ 而做完之後想一下:為什麼「每次選最小的兩堆」一定是對的?

這一課你做了什麼

如果卡住了

你看到 多半是因為
拿到的是最小的 用了 greater 版本。預設是大的先出
greater 那個宣告編譯錯 中間的 vector<int> 不能省
想走訪卻沒有 begin() 它不是有序容器。要全部有序就用 sort
貪心答案錯 那題不能貪心。想想第 10 課
在編輯器打開這一課 →