STL 之 優先佇列(priority_queue)
1、什麼是優先佇列
能夠完成下列兩種操作的資料結構,我們便稱之為優先佇列。
①插入一個數值 ②取出最大(或者最小)的數值(獲取數值,並且刪除)。
從嚴格意義上來說優先佇列,並不是佇列,因為它並不遵循佇列的FIFO(先進先出的原則)。
2、實現優先佇列
我們可以使用一種叫做“堆(heap)”的資料結構來實現優先佇列。堆有一個重要的性質就是兒子的值一定不小於父親。除此之外,樹的節點是從上到下、從左到右的順序緊湊排列的。堆就是如下圖的二叉樹, 不知道是什麼是二叉樹的同學請移步:傳送門
我們向堆插入數值時,首先我們先在堆的尾部插入該值,然後再根據大小關係不斷的提升它的位置
刪除堆的最小值時,首先把堆的最後一個節點複製到根節點上,然後刪除最後一個節點。之後我們根據大小關係不斷和交換位置,使其滿足堆的定義。
堆的這兩種操作所用的時間,和樹的深度成正比。我們不難發現堆的時間複雜度為O(log n).
現在我們就可以來實現堆了,為了簡單一些我們使用陣列來實現:
int heap[MAXN], size_heap = 0; //插入數值 void push(int x){ int i = size_heap++; while(i > 0){ //父節點的編號 int p = (i-1)/2; //如果大小關係滿足,則退出迴圈 if(heap[p] <= x) break; //將父節點放下,把自己向上提 heap[i] = heap[p]; i = p; } heap[i] = x; } //獲取最小值,並刪除最小值 int top(){ //最小值 int ret = heap[0]; //最後一個節點 int x = heap[--size_heap]; int i = 0; while(2*i+1 < size_heap){ int a = 2*i+1, b = 2*i+2; //比較兩個兒子的值 if(b < size_heap && heap[b] < heap[a]) a = b; //滿足大小關係 if(heap[a] >= x) break; //將數值小的那個兒子提上來 heap[i] = heap[a]; i = a; } heap[i] = x; return ret; }
通過實現堆,我們就大致的對優先佇列的原理有了一定的瞭解。
3、STL之priority_queue
然而很多時候我們並不需要自己實現堆。C++為我們提供了模板類priority_queue。STL的priority_queue包含在標頭檔案queue中, 由於priority_queue使用堆實現,所以我們可以知道priority_queue的時間複雜度應該也為 O(log n)。不過和上述堆實現的優先佇列有些許不同。因為priority_queue取出數值時是最大值。我們來看看priority_queue的用法。
<1>priority_queue的基本操作
priority_queue我們常用的有四個成員函式分別
bool empty() const; | 返回值為true,則該優先佇列為空,反之亦然 |
size_type size() const; | 返回優先佇列中元素的數量,size_type是unsigned integral type |
void pop(); | 刪除佇列頂部的元素,也就是根節點 |
void push (const value_type& val); | 將元素加入,優先佇列中 |
const_reference top() const; | 返回佇列頂部的元素,const_reference為佇列頂部的型別 |
<2>改變priority_queue中的排列順序
在很多時候,我們需要的不一定是最大值,也有可能是最小值。這是就需要我們來改變priority_queue中的順序。方法有兩種:
①如果加入優先佇列的是基本型別,那麼我們就可以這樣,我們以int為例:
//注意greater<int> >這之間有一個空格
priority_queue<int, vector<int>, greater<int> >Q;
②對於自定義資料型別的話,我們不論是要改變排序方式,還是不改變都要這樣 -- 過載 小於( < ) 運算子:
因為,如果你不過載比較運算子的話,編譯器無法比較自定義資料型別的大小關係。然而又因為在priority_queue的內部,只需用到 小於號(<),所以我們只需要過載小於號即可。當然對於自定義資料型別來說,也是必須過載,否則將無法使用priority_queue。過載小於號,我們可以有兩種方式,一種用成員函式,一種使用友元函式(這裡就不多說了,不會的同學,自己在好好複習複習C++)。
注意:如果使用成員函式過載小於號的話,那麼要將過載函式變為常成員函式,否則將無法通過編譯。
初步的瞭解了優先佇列之後,我們就可以來兩道題鞏固,鞏固。Expedition,
FenceRepair。這兩道都是非常有意思的題目,思路比較奇特。