【C++】競程筆記(資料結構:Heap)
【C++】競程筆記(資料結構:Heap)
程式碼範例參考:NTUCPC Guide,此筆記僅為個人學習用途。
簡介
Heap(堆積),是一個完全二元樹的資料結構,主要應用於優先佇列(priority queue)中。
由於他是完全二元樹結構,所以:
- 除了最底層外,其他每一層都是滿的。
- 最底層節點從左到右依序填滿。
樹上的每一個節點儲存一個可以比較大小的鍵值(key),並支援以下操作:
- 插入一個鍵值
- 詢問目前最大的鍵值
- 刪除最大的鍵值
From NTUCPC Guide
Heap 通常遵守下列其中一種堆積性質:
- 最小堆(Min-Heap):每個節點的值都 $\leq$ 其子節點的值。(根節點是整棵樹中最小的元素)
- 最大堆(Max-Heap):每個節點的值都 $\ge$ 其子節點的值。(根節點是整棵樹中最大的元素)

Image Source:https://www.geeksforgeeks.org/dsa/heap-data-structure/
以下網站有可以滑動的視窗,展示哪一種是 invalid heap,哪一種是 valid heap。
https://www.geeksforgeeks.org/dsa/heap-data-structure/
Binary Heap 實作方式
大致上兩種:
- 樹狀結構(不常見)
- 陣列形式(常用)
(預設根節點的 index = 0)若某節點在索引 i:
- 左子節點在
2i + 1 - 右子節點在
2i + 2 - 父節點在
(i - 1) / 2
在此使用 vector 實作(最小堆):
- int getMin()
代表回傳堆疊根節點的值(也就是最小值),複雜度 $O(1)$ 。
1 | // From NTUCPC Guide |
- void insert(int x)
要加入一個值為 $x$ 的數字進去 Heap 裡面。作法就是先將 $x$ 先丟進去最下面,然後一直往上,只要發現 $x$ 現在的節點比其父節點的值小,就交換他們兩個,並繼續看。
1 | // From NTUCPC Guide |
- void deleteRoot(int x)
刪除根節點。將根換成最後的節點(最後的節點也刪掉),然後開始遞迴往下:如果目前的值比左右子樹的最小值大,則與左右子樹的最小值交換,並且往那個子樹遞迴下去。
1 | // From NTUCPC Guide |
insert()、deleteRoot() 時間複雜度均為 $O(logn)$ 。
STL in Heap
使用前先引入標頭檔 <queue>。
語法:
1 | priority_queue<T, c, comp> pq; |
T:優先佇列的型態。
pq:優先佇列名稱。
c:底層容器。預設使用 vector。
comp:比較函數。
priority_queue 為最大堆(max-heap)容器配接器,預設會將最大值放在頂端,若要將最小值放到頂端,則將 comp 改為 greater<int>,如下所示:
1 | priority_queue<int, vector<int>, greater<int>> minHeap; |
常見操作
- push():將資料丟入堆積內部
- pop():移除堆積頂端元素。
- top():回傳堆積頂部元素(不刪除)。
- size():回傳堆積元素數量。
- empty():檢查堆積是否為空。
小範例:
1 |
|
Output:
1 | 20 10 5 |
習題練習
排隊買飲料
Source:https://tioj.ck.tp.edu.tw/problems/1999
限制條件:
- 每個店員一次只能服務一名顧客。
- 店員必須按照順序接客。
- 每位顧客所點飲料表示服務時間。
- 每個店員的處理時間皆相同(1 min / 杯)。
解題思路:
- 用最小堆處理店員的「忙碌時間結束點」。
- 每位店員的初始可用時間 = 0。
- 按照排隊順序,依序將每位顧客交給「目前最早可用的店員」:
完成時間 = 店員目前可用時間 + 顧客需要的時間- 將店員的可用時間更新為該完成時間,並放回堆中。
最後輸出 max_time。
19 行中的 min(M, N) 是為了考慮效能,如果直接寫 M 會造成效能浪費,舉例:
假設 M = 10000,N = 5。
然而你卻最多只用到 5 個店員,9995個店員沒用到,不就造成效能浪費嗎?
1 |
|




