# 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`