# Python 及 C++ 優先佇列 (priority queue) > 作者:王一哲 > 日期:2023年12月31日 ## 前言 優先佇列 (priority queue) 又稱為堆積佇列 (heap queue),Python 及 C++ 都有對應的容器,不過 Python 會將最小值放在佇列最上面,而 C++ 預設則是將最大值放在佇列最上面。以下的程式碼測試版本為 Python 3.10.12 及 C++14。 <br /> ## Python 語法 如果要在 Python 中使用優先佇列,需要引入函式庫 heapq;如果是在 LeetCode 網站上解題,因為網站已經引入了 heapq,不需要自己引入 heapq。語法為 ```python import heapq import * ``` 由於 Python heapq 的特性,使用時可以將它視為串列,初始化一個 heap 時,語法通常為 ```python h = [] ``` <br /> ### 將資料放入 heap 語法為 ```python heapq.heappush(heap, 資料) ``` 例如以下的程式碼 ```python h = [] heapq.heappush(h, 3) # h 的內容為 [3] heapq.heappush(h, 1) # h 的內容為 [1, 3] heapq.heappush(h, 2) # h 的內容為 [1, 3, 2] ``` <br /> ### 從 heap 中取出並回傳最小的資料 語法為 ```python heapq.heappop(heap) ``` 如果 heap 是空的則會回傳錯誤訊息 IndexError。例如以下的程式碼,若 h 的內容為 [1, 3, 2],則 ```python 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 ``` <br /> ### 將資料放入 heap 再取出並回傳最小的資料 語法為 ```python heapq.heappushpop(heap, 資料) ``` 效果相當於先呼叫 heappush 再呼叫 heappop,但是執行效率比較好。例如以下的程式碼,若 h 的內容為 [1, 3, 2],則 ```python n = heapq.heappushpop(h, 4) # 回傳1,h 的內容變為 [2, 3, 4] ``` <br /> ### 從 heap 中取出並回傳最小的資料再將另一筆資料放入 heap 語法為 ```python heapq.heapreplace(heap, 資料) ``` 效果相當於先呼叫 heappop 再呼叫 heappush,但是執行效率比較好。如果 heap 是空的會回傳錯誤訊息 IndexError。例如以下的程式碼,若 h 的內容為 [1, 3, 2],則 ```python n = heapq.heapreplace(h, 0) # 回傳1,h 的內容變為 [0, 3, 2] ``` <br /> ### 將串列轉為 heap 語法為 ```python heapq.heapify(串列名稱) ``` 例如以下的程式碼 ```python x = [5, 1, 4, 2, 3] heapq.heapify(x) # x 變為 [1, 2, 4, 5, 3] ``` <br /> ### 合併多個已排序的串列為一個 heap 語法為 ```python heapq.merge(串列1, 串列2, ..., key=None, reverse=False) ``` 輸入的串列可以有好幾個。key 參數是比較依據,預設值為 None,直接比較資料本身。reverse 預設值為 Fasle、由小到大排序,輸入的串列也要由小到大排序。要做到同樣的效果,也可以使用 sorted 及 itertools.chain,例如以下的程式碼 ```python 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] ``` <br /> reverse 也可以改成 True、由大到小排序,輸入的串列也要由大到小排序。要做到同樣的效果,也可以使用 sorted 及 itertools.chain,例如以下的程式碼 ```python 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] ``` <br /> ### 回傳一個串列中前 n 個最大值 語法為 ```python heapq.nlargest(n, 串列, key=None) ``` key 參數是比較依據,預設值為 None。例如以下的程式碼 ```python a = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6] n = heapq.nlargest(5, a) # 回傳值為 [10, 9, 8, 7, 6] ``` <br /> ### 回傳一個串列中前 n 個最小值 語法為 ```python heapq.nsmallest(n, 串列, key=None) ``` key 參數是比較依據,預設值為 None。例如以下的程式碼 ```python a = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6] n = heapq.nsmallest(5, a) # 回傳值為 [1, 2, 3, 4, 5] ``` <br /> ## C++ 語法 如果要在 C++ 中使用優先佇列,需要引入函式庫 queue,語法為 ```cpp #include <quque> ``` <br /> 産生優先佇列 priority_queue 時,通常會從一個已經存在的一維 array 或 vector 輸入資料,預設的排序方式為由大到小,如果要改成由小到大則需要再加上 functional 函式庫中的 greater,例如以下的程式碼 ```cpp= #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; } ``` <br /> ### 檢查 priority_queue 是否是空的 語法為 ```cpp 名稱.empty(); ``` 如果是空的回傳1,如果不是空的回傳0。例如以下的程式碼,假設 b 是空的,c 的內容為 {5, 4, 3, 2, 1},則 ```cpp cout << b.empty() << "\n"; // 印出1 cout << c.empty() << "\n"; // 印出0 ``` <br /> ### 回傳 priority_queue 的長度 語法為 ```cpp 名稱.size(); ``` 例如以下的程式碼,假設 b 是空的,c 的內容為 {5, 4, 3, 2, 1},則 ```cpp cout << b.size() << "\n"; // 印出0 cout << c.size() << "\n"; // 印出5 ``` <br /> ### 回傳 priority_queue 最上面的資料 語法為 ```cpp 名稱.top(); ``` 例如以下的程式碼,假設 c 的內容為 {5, 4, 3, 2, 1},則 ```cpp cout << c.top() << "\n"; // 印出5 ``` <br /> ### 將資料加入 priority_queue 有兩種工具,語法為 ```cpp 名稱.push(資料); 名稱.emplace(資料); ``` 例如以下的程式碼,假設 c 的內容為 {5, 4, 3, 2, 1},則 ```cpp c.push(0); // 內容為 {5, 4, 3, 2, 1, 0} c.emplace(-1); // 內容為 {5, 4, 3, 2, 1, 0, -1} ``` <br /> ### 移除 priority_queue 最上方的資料 語法為 ```cpp 名稱.pop(); ``` 例如以下的程式碼,假設 c 的內容為 {5, 4, 3, 2, 1},則 ```cpp c.pop(); // 內容變為 {4, 3, 2, 1} ``` <br /> ### 交換兩個 priority_queue 的資料 語法為 ```cpp 名稱1.swap(名稱2); ``` 例如以下的程式碼,假設 c 的內容為 {9, 7, 5, 3, 1},d 的內容為 {8, 6, 4, 2},則 ```cpp c.swap(d); // c, d 互換資料 ``` <br /> ### 常用技巧:依序印出 priority_queue 所有的資料 假設 c 的內容為 {9, 7, 5, 3, 1},用以下的程式碼可以依序印出 c 所有的資料 ```cpp while(!c.empty()) { cout << c.top() << "\n"; c.pop(); } ``` <br /> ## 結語 某些題目只要求將最大或最小的資料排在佇列最上方,其它資料不需要排序,這時使用優先佇列儲存資料特別方便。以上只是我目前會用到的功能,如果以後有遇到其它用法會再補充。 <br /> ## 參考資料 1. [heapq --- 堆積佇列 (heap queue) 演算法](https://docs.python.org/zh-tw/3/library/heapq.html) 2. [cplusplus.com std::priority_queue](https://cplusplus.com/reference/queue/priority_queue/) --- ###### tags:`C++`、`Python`