1. 程式人生 > >STL 之 優先佇列(priority_queue)

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++)。

注意:如果使用成員函式過載小於號的話,那麼要將過載函式變為常成員函式,否則將無法通過編譯。

        初步的瞭解了優先佇列之後,我們就可以來兩道題鞏固,鞏固。ExpeditionFenceRepair。這兩道都是非常有意思的題目,思路比較奇特。