熱門文章

2023年12月31日 星期日

Python 及 C++ 優先佇列 (priority queue)

作者:王一哲
日期:2023年12月31日



前言


優先佇列 (priority queue) 又稱為堆積佇列 (heap queue),Python 及 C++ 都有對應的容器,不過 Python 會將最小值放在佇列最上面,而 C++ 預設則是將最大值放在佇列最上面。以下的程式碼測試版本為 Python 3.10.12 及 C++14。


Python 語法


如果要在 Python 中使用優先佇列,需要引入函式庫 heapq;如果是在 LeetCode 網站上解題,因為網站已經引入了 heapq,不需要自己引入 heapq。語法為
import heapq
由於 Python heapq 的特性,使用時可以將它視為串列,初始化一個 heap 時,語法通常為
h = []

將資料放入 heap


語法為
heapq.heappush(heap, 資料)
例如以下的程式碼
h = []
heapq.heappush(h, 3)  # h 的內容為 [3]
heapq.heappush(h, 1)  # h 的內容為 [1, 3]
heapq.heappush(h, 2)  # h 的內容為 [1, 3, 2]

從 heap 中取出並回傳最小的資料


語法為
heapq.heappop(heap)
如果 heap 是空的則會回傳錯誤訊息 IndexError。例如以下的程式碼,若 h 的內容為 [1, 3, 2],則
n = heapq.heappop(h)   # 回傳1,h 的內容變為 [2, 3]
n = heapq.heappop(h)   # 回傳2,h 的內容變為 [3]
n = heapq.heappop(h)   # 回傳3,h 已經是空的
n = heapq.heappop(h)   # 回傳錯誤訊息 IndexError

將資料放入 heap 再取出並回傳最小的資料


語法為
heapq.heappushpop(heap, 資料)
效果相當於先呼叫 heappush 再呼叫 heappop,但是執行效率比較好。例如以下的程式碼,若 h 的內容為 [1, 3, 2],則
n = heapq.heappushpop(h, 4)   # 回傳1,h 的內容變為 [2, 3, 4]

從 heap 中取出並回傳最小的資料再將另一筆資料放入 heap


語法為
heapq.heapreplace(heap, 資料)
效果相當於先呼叫 heappop 再呼叫 heappush,但是執行效率比較好。如果 heap 是空的會回傳錯誤訊息 IndexError。例如以下的程式碼,若 h 的內容為 [1, 3, 2],則
n = heapq.heapreplace(h, 0)   # 回傳1,h 的內容變為 [0, 3, 2]

將串列轉為 heap


語法為
heapq.heapify(串列名稱)
例如以下的程式碼
x = [5, 1, 4, 2, 3]
heapq.heapify(x)   # x 變為 [1, 2, 4, 5, 3]

合併多個已排序的串列為一個 heap


語法為
heapq.merge(串列1, 串列2, ..., key=None, reverse=False)
輸入的串列可以有好幾個。key 參數是比較依據,預設值為 None,直接比較資料本身。reverse 預設值為 Fasle、由小到大排序,輸入的串列也要由小到大排序。要做到同樣的效果,也可以使用 sorted 及 itertools.chain,例如以下的程式碼
import itertools
a = [1, 3, 5, 7, 9]
b = [2, 4, 6, 8, 10]
c = list(heapq.merge(a, b))
d = sorted(itertools.chain(a, b))
# c, d 皆為 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

reverse 也可以改成 True、由大到小排序,輸入的串列也要由大到小排序。要做到同樣的效果,也可以使用 sorted 及 itertools.chain,例如以下的程式碼
import itertools
a = [9, 7, 5, 3, 1]
b = [10, 8, 6, 4, 2]
c = list(heapq.merge(a, b, reverse=True))
d = sorted(itertools.chain(a, b), reverse=True)
# c, d 皆為 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

回傳一個串列中前 n 個最大值


語法為
heapq.nlargest(n, 串列, key=None)
key 參數是比較依據,預設值為 None。例如以下的程式碼
a = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6]
n = heapq.nlargest(5, a)   # 回傳值為 [10, 9, 8, 7, 6]

回傳一個串列中前 n 個最小值


語法為
heapq.nsmallest(n, 串列, key=None)
key 參數是比較依據,預設值為 None。例如以下的程式碼
a = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6]
n = heapq.nsmallest(5, a)   # 回傳值為 [1, 2, 3, 4, 5]


C++ 語法


如果要在 C++ 中使用優先佇列,需要引入函式庫 queue,語法為
#include <quque>

産生優先佇列 priority_queue 時,通常會從一個已經存在的一維 array 或 vector 輸入資料,預設的排序方式為由大到小,如果要改成由小到大則需要再加上 functional 函式庫中的 greater,例如以下的程式碼
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;

int main() {
    int a[] = {5, 1, 4, 2, 3};
    vector<int> v (a, a+5);
    priority_queue<int> b;  // 空的
    priority_queue<int> c (a, a+5);  // 內容為 {5, 4, 3, 2, 1}
    priority_queue<int> d (v.begin(), v.end());  // 內容為 {5, 4, 3, 2, 1}
    priority_queue<int, vector<int>, greater<int>> e (a, a+5);  // 內容為 {1, 2, 3, 4, 5}
    return 0;
}

檢查 priority_queue 是否是空的


語法為
名稱.empty();
如果是空的回傳1,如果不是空的回傳0。例如以下的程式碼,假設 b 是空的,c 的內容為 {5, 4, 3, 2, 1},則
cout << b.empty() << "\n";  // 印出1
cout << c.empty() << "\n";  // 印出0

回傳 priority_queue 的長度


語法為
名稱.size();
例如以下的程式碼,假設 b 是空的,c 的內容為 {5, 4, 3, 2, 1},則
cout << b.size() << "\n";  // 印出0
cout << c.size() << "\n";  // 印出5

回傳 priority_queue 最上面的資料


語法為
名稱.top();
例如以下的程式碼,假設 c 的內容為 {5, 4, 3, 2, 1},則
cout << c.top() << "\n";  // 印出5

將資料加入 priority_queue


有兩種工具,語法為
名稱.push(資料);
名稱.emplace(資料);
例如以下的程式碼,假設 c 的內容為 {5, 4, 3, 2, 1},則
c.push(0);  // 內容為 {5, 4, 3, 2, 1, 0}
c.emplace(-1);  // 內容為 {5, 4, 3, 2, 1, 0, -1}

移除 priority_queue 最上方的資料


語法為
名稱.pop();
例如以下的程式碼,假設 c 的內容為 {5, 4, 3, 2, 1},則
c.pop();  // 內容變為 {4, 3, 2, 1}

交換兩個 priority_queue 的資料


語法為
名稱1.swap(名稱2);
例如以下的程式碼,假設 c 的內容為 {9, 7, 5, 3, 1},d 的內容為 {8, 6, 4, 2},則
c.swap(d);  // c, d 互換資料

常用技巧:依序印出 priority_queue 所有的資料


假設 c 的內容為 {9, 7, 5, 3, 1},用以下的程式碼可以依序印出 c 所有的資料
while(!c.empty()) {
    cout << c.top() << "\n";
    c.pop();
}


結語


某些題目只要求將最大或最小的資料排在佇列最上方,其它資料不需要排序,這時使用優先佇列儲存資料特別方便。以上只是我目前會用到的功能,如果以後有遇到其它用法會再補充。


參考資料


  1. heapq --- 堆積佇列 (heap queue) 演算法
  2. cplusplus.com std::priority_queue




HackMD 版本連結:https://hackmd.io/@yizhewang/S1EBLEAvT

沒有留言:

張貼留言