1. 程式人生 > >STL之heap與優先順序佇列Priority Queue詳解

STL之heap與優先順序佇列Priority Queue詳解

一、heap

             heap並不屬於STL容器元件,它分為 max heap 和min heap,在預設情況下,max-heap是優先佇列(priority queue)的底層實現機制。而這個實現機制中的max-heap實際上 是以一個vector表現的完全二叉樹(complete binary tree)。STL在<algorithm.h>中實現了對 儲存在vector/deque 中的元素進行堆操作的函式,包括make_heap, pop_heap, push_heap, sort_heap,對不願自己寫資料結構堆的C++選手來說,這幾個演算法函式很有用,詳細解釋可以參見: 

http://www.cplusplus.com/reference/algorithm/make_heap/

下面的_First與_Last為可以隨機訪問的迭代器(指標),_Comp為比較函式(仿函式),其規則——如果函式的第一個引數小於第二個引數應返回true,否則返回false。

1.make_heap():

make_heap(_First, _Last)

make_heap(_First, _Last, _Comp)

預設是建立最大堆的。對int型別,可以在第三個引數傳入greater<int>()得到最小堆。

2.push_heap(_First, _Last):

新新增一個元素在末尾,然後重新調整堆序。也就是把元素新增在底層vector的end()處。

該演算法必須是在一個已經滿足堆序的條件下,新增元素。該函式接受兩個隨機迭代器,分別表示first,end,區間範圍。

關鍵是我們執行一個siftup()函式,上溯函式來重新調整堆序。具體的函式機理很簡單,可以參考我的程式設計珠璣裡面堆的實現的文章。

3.pop_heap(_First, _Last):

這個演算法跟push_heap類似,引數一樣。不同的是我們把堆頂元素取出來,放到了陣列或者是vector的末尾,用原來末尾元素去替代,然後end迭代器減1,執行siftdown()下溯函式來重新調整堆序。

注意演算法執行完畢後,最大的元素並沒有被取走,而是放於底層容器的末尾。如果要取走,則可以使用底部容器(vector)提供的pop_back()函式。

4.sort_heap(_First, _Last):

既然每次pop_heap可以獲得堆中最大的元素,那麼我們持續對整個heap做pop_heap操作,每次將操作的範圍向前縮減一個元素。當整個程式執行完畢後,我們得到一個非降的序列。注意這個排序執行的前提是,在一個堆上執行。

下面是這幾個函式操作vector中元素的例子。

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

int main()
{
  int a[] = {15, 1, 12, 30, 20};
  vector<int> ivec(a, a+5);
  for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
    cout<<*iter<<" ";
  cout<<endl;

  make_heap(ivec.begin(), ivec.end());//建堆
  for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
    cout<<*iter<<" ";
  cout<<endl;

  pop_heap(ivec.begin(), ivec.end());//先pop,然後在容器中刪除
  ivec.pop_back();
  for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
    cout<<*iter<<" ";
  cout<<endl;

  ivec.push_back(99);//先在容器中加入,再push
  push_heap(ivec.begin(), ivec.end());
  for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
    cout<<*iter<<" ";
  cout<<endl;

  sort_heap(ivec.begin(), ivec.end());
  for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
    cout<<*iter<<" ";
  cout<<endl;

  return 0;
}

二、priority queue

       優先佇列(priority_queue)首先是一個queue,那就是必須在末端推入,必須在頂端取出元素。除此之外別無其他存取元素的途徑。內部元素按優先順序高低排序,優先順序高的在前。預設情況下,priority_heap利用一個max-heap完成,後者是一個以vector表現的完全二叉樹。我們說優先佇列不是一個STL容器,它以底部容器而實現,修改了介面,形成另一種性質,這樣的東西稱之為介面卡(adapter)。

優先順序佇列是一個擁有權值觀念的queue。它允許在底端新增元素、在頂端去除元素、刪除元素。

優先順序佇列內部的元素並不是按照新增的順序排列,而是自動依照元素的權值排列。權值最高者排在最前面。

預設情況下,優先順序佇列利用一個大頂堆完成。關於堆可以參考:STL堆詳解與程式設計實現

優先順序佇列以底部容器完成其所有工作,具有這種“修改某物介面,形成另一種風貌”這種性質者,成為配接器(adapter)。在STL中優先順序佇列不被歸類為容器,而被歸類為容器配接器(container adapter)
priority_queue 對於基本型別的使用方法相對簡單。

他的模板宣告帶有三個引數,priority_queue<Type, Container, Functional>
Type 為資料型別, Container 為儲存資料的容器,Functional 為元素比較方式。
Container 必須是用陣列實現的容器,比如 vector, deque 但不能用 list.
STL裡面容器預設用的是 vector. 比較方式預設用 operator< , 所以如果你把後面倆個引數 預設的話,優先佇列就是大頂堆,隊頭元素最大。
看例子

#include <iostream>
#include <queue>
using namespace std;
int main(){
    priority_queue<int,vector<int>,less<int> >q;//使用priority_queue<int> q1;一樣
    for(int i=0;i<10;i++) 
		q1.push(i);
    while(!q1.empty()){
        cout<<q1.top()<< endl;
        q1.pop();
    }
    return 0;
}
如果要用到小頂堆,則一般要把模板的三個引數都帶進去。
STL裡面定義了一個仿函式 greater<>,對於基本型別可以用這個仿函式宣告小頂堆
例子:
#include <iostream>
#include <queue>
using namespace std;
int main(){
    priority_queue<int,vector<int>,greater<int> >q;
    for(int i=0;i<10;i++) 
		q.push(i);
    while(!q.empty()){
        cout<<q.top()<< endl;
        q.pop();
    }
    return 0;
}
對於自定義型別,則必須自己過載 operator< 或者自己寫仿函式先看看例子:
#include <iostream>
#include <queue>
using namespace std;
struct Node{
	int x, y;
}node;
 bool operator<( Node a, Node b){
    if(a.x==b.x) return a.y>b.y;
    return a.x>b.x;
}
 int main(){
    priority_queue<Node>q;
    for(int i=0;i<10;i++){
    	node.x=i;
    	node.y=10-i/2;
    	q.push(node);
    }	
    while(!q.empty()){
        cout<<q.top().x <<' '<<q.top().y<<endl;
        q.pop();
    }
    return 0;
}
自定義型別過載 operator< 後,宣告物件時就可以只帶一個模板引數。
此時不能像基本型別這樣宣告priority_queue<Node, vector<Node>, greater<Node> >;
原因是 greater<Node> 沒有定義,如果想用這種方法定義
則可以按如下方式例子:(個人喜歡這種方法,因為set的自定義比較函式也可以寫成這種形式
#include <iostream>
#include <queue>
using namespace std;
struct Node{
	int x, y;
}node;
struct cmp{
    bool operator()(Node a,Node b){
        if(a.x==b.x) return a.y>b.y;
        return a.x>b.x;}
};

 int main(){
    priority_queue<Node,vector<Node>,cmp>q;
    for(int i=0;i<10;i++){
    	node.x=i;
    	node.y=10-i/2;
		q.push(node);	
    }	
    while(!q.empty()){
        cout<<q.top().x<<' '<<q.top().y<<endl;
        q.pop();
    }
    return 0;
}

SGI STL中優先順序佇列定義

定義完整程式碼:

    template <class T, class Sequence = vector<T>,   
              class Compare = less<typename Sequence::value_type> >   
    class priority_queue {   
    public:   
      typedef typename  Sequence::value_type  value_type;   
      typedef typename  Sequence::size_type  size_type;   
      typedef typename  Sequence::reference  reference;   
      typedef typename  Sequence::const_referenceconst_reference;   
    protected:   
    Sequence c;  //底層容器   
        Compare comp;//元素大小比較標準   
    public:   
    priority_queue() : c() {}   
      explicit  priority_queue(const Compare& x) :  c(), comp(x) {}   
       
    //以下用到的 make_heap(), push_heap(), pop_heap()都是泛型演算法   
    //注意,任一個建構式都立刻於底層容器內產生一個 implicit representation heap。   
      template  <class  InputIterator>   
    priority_queue(InputIterator first, InputIterator last, const Compare& x)   
        : c(first, last), comp(x) {make_heap(c.begin(), c.end(), comp); }   
      template  <class  InputIterator>   
    priority_queue(InputIterator first, InputIterator last)   
        : c(first,  last)  { make_heap(c.begin(), c.end(), comp); }   
       
      bool empty() const { return c.empty(); }   
      size_typesize() const { return c.size(); }   
      const_referencetop() const { return c.front(); }   
      void push(const value_type& x) {   
        __STL_TRY {   
    // push_heap是泛型演算法,先利用底層容器的 push_back() 將新元素   
          // 推入᳿端,再重排 heap。見 C++ Primer p.1195。   
    c.push_back(x);   
    push_heap(c.begin(), c.end(), comp);// push_heap是泛型演算法   
        }   
        __STL_UNWIND(c.clear());   
      }   
      void pop() {   
        __STL_TRY {   
    // pop_heap 是泛型演算法,從 heap 內取出一個元素。它並不是真正將元素   
          // 彈出,而是重排 heap,然後再以底層容器的 pop_back() 取得被彈出   
          // 的元素。見 C++ Primer p.1195。   
    pop_heap(c.begin(), c.end(), comp);   
    c.pop_back();   
        }   
        __STL_UNWIND(c.clear());   
      }   
    };   

優先順序佇列程式設計實現(C Plus Plus)

在這裡自己用C++程式設計實現了簡易版的優先順序佇列。

其中用到了前一篇部落格裡面的堆heap.h:

    //STL堆演算法實現(大頂堆)  
      
    //包含容器vector的標頭檔案:Heap用vector來儲存元素  
    #include <vector>  
    #include <iostream>  
    #include <functional>  
      
    #define MAX_VALUE 999999 //某個很大的值,存放在vector的第一個位置(最大堆)  
      
    const int StartIndex = 1;//容器中堆元素起始索引  
      
    using namespace std;  
      
    //堆類定義  
    //預設比較規則less  
    template <class ElemType,class Compare = less<ElemType> >  
    class MyHeap{  
    private:  
        vector<ElemType> heapDataVec;//存放元素的容器  
        int numCounts;//堆中元素個數  
        Compare comp;//比較規則  
      
    public:  
        MyHeap();  
      
        vector<ElemType>& getVec();  
      
        void initHeap(ElemType *data,const int n);//初始化操作  
        void printfHeap();//輸出堆元素  
        void makeHeap();//建堆  
        void sortHeap();//堆排序演算法  
        void pushHeap(ElemType elem);//向堆中插入元素  
        void popHeap();//從堆中取出堆頂的元素  
        void adjustHeap(int childTree,ElemType adjustValue);//調整子樹  
        void percolateUp(int holeIndex,ElemType adjustValue);//上溯操作  
      
        void setNumCounts(int val);//設定當前所要構建的堆中元素個數  
    };  
      
    template <class ElemType,class Compare>  
    MyHeap<ElemType,Compare>::MyHeap()  
    :numCounts(0)  
    {  
        heapDataVec.push_back(MAX_VALUE);  
    }  
      
    template <class ElemType,class Compare>  
    vector<ElemType>& MyHeap<ElemType,Compare>::getVec()  
    {  
        return heapDataVec;  
    }  
      
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::initHeap(ElemType *data,const int n)  
    {  
        //拷貝元素資料到vector中  
        for (int i = 0;i < n;++i)  
        {  
            heapDataVec.push_back(*(data + i));  
            ++numCounts;  
        }  
    }  
      
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::printfHeap()  
    {  
        cout << "Heap : ";  
        for (int i = 1;i <= numCounts;++i)  
        {  
            cout << heapDataVec[i] << " ";  
        }  
        cout << endl;  
    }  
      
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::makeHeap()  
    {  
        //建堆的過程就是一個不斷調整堆的過程,迴圈呼叫函式adjustHeap依次調整子樹  
        if (numCounts < 2)  
            return;  
        //第一個需要調整的子樹的根節點多音  
        int parent = numCounts / 2;  
        while(1)  
        {  
            adjustHeap(parent,heapDataVec[parent]);  
            if (StartIndex == parent)//到達根節點  
                return;  
      
            --parent;  
        }  
    }  
      
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::sortHeap()  
    {  
        //堆排序思路  
        //每執行一次popHeap操作,堆頂的元素被放置在尾端,然後針對前面的一次再執行popHeap操作  
        //依次下去,最後即得到排序結果  
        while(numCounts > 0)  
            popHeap();  
    }  
      
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::pushHeap(ElemType elem)  
    {  
        //將新元素新增到vector中  
        heapDataVec.push_back(elem);  
        ++numCounts;  
      
        //執行一次上溯操作,調整堆,以使其滿足最大堆的性質  
        percolateUp(numCounts,heapDataVec[numCounts]);  
    }  
      
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::popHeap()  
    {  
        //將堆頂的元素放在容器的最尾部,然後將尾部的原元素作為調整值,重新生成堆  
        ElemType adjustValue = heapDataVec[numCounts];  
        //堆頂元素為容器的首元素  
        heapDataVec[numCounts] = heapDataVec[StartIndex];  
        //堆中元素數目減一  
        --numCounts;  
      
        adjustHeap(StartIndex,adjustValue);  
    }  
      
    //調整以childTree為根的子樹為堆  
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::adjustHeap(int childTree,ElemType adjustValue)  
    {  
        //洞節點索引  
        int holeIndex = childTree;  
        int secondChid = 2 * holeIndex + 1;//洞節點的右子節點(注意:起始索引從1開始)  
        while(secondChid <= numCounts)  
        {  
            if (comp(heapDataVec[secondChid],heapDataVec[secondChid - 1]))  
            {  
                --secondChid;//表示兩個子節點中值較大的那個  
            }  
      
            //上溯  
            heapDataVec[holeIndex] = heapDataVec[secondChid];//令較大值為洞值  
            holeIndex = secondChid;//洞節點索引下移  
            secondChid = 2 * secondChid + 1;//重新計算洞節點右子節點  
        }  
        //如果洞節點只有左子節點  
        if (secondChid == numCounts + 1)  
        {  
            //令左子節點值為洞值  
            heapDataVec[holeIndex] = heapDataVec[secondChid - 1];  
            holeIndex = secondChid - 1;  
        }  
        //將調整值賦予洞節點  
        heapDataVec[holeIndex] = adjustValue;  
      
        //此時可能尚未滿足堆的特性,需要再執行一次上溯操作  
        percolateUp(holeIndex,adjustValue);  
    }  
      
    //上溯操作  
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::percolateUp(int holeIndex,ElemType adjustValue)  
    {  
        //將新節點與其父節點進行比較,如果鍵值比其父節點大,就父子交換位置。  
        //如此,知道不需要對換或直到根節點為止  
        int parentIndex = holeIndex / 2;  
        while(holeIndex > StartIndex && comp(heapDataVec[parentIndex],adjustValue))  
        {  
            heapDataVec[holeIndex] = heapDataVec[parentIndex];  
            holeIndex = parentIndex;  
            parentIndex /= 2;  
        }  
        heapDataVec[holeIndex] = adjustValue;//將新值放置在正確的位置  
    }  
      
    template <class ElemType,class Compare>  
    void MyHeap<ElemType,Compare>::setNumCounts(int val)  
    {  
        numCounts = val;  
    }  

PriorityQueue.h:
    #include "Heap.h"  
      
    //優先順序佇列類定義  
    //預設:值最小的權值最大  
    template <class ElemType,class Compare = less<ElemType> >  
    class MyPriorityQueue{  
    private:  
        MyHeap<ElemType,Compare> heap;//底層用堆實現  
      
    public:  
        //建構函式  
        MyPriorityQueue(ElemType *data,int n);  
      
        //判斷優先順序佇列是否為空  
        int empty(){return heap.getVec().size() - 1;}  
        //返回優先順序佇列大小  
        long size(){return heap.getVec().size() - 1;}//注意底層容器第一個元素是無效元素  
        //取得優先順序佇列頭元素  
        ElemType top(){return heap.getVec()[StartIndex];}  
        //新增元素  
        void push(const ElemType &val);  
        //彈出隊首元素  
        void pop();  
        MyHeap<ElemType,Compare>& getHeap(){return heap;};  
    };  
      
    template <class ElemType,class Compare>  
    MyPriorityQueue<ElemType,Compare>::MyPriorityQueue(ElemType *data, int n)  
    {  
        heap.initHeap(data,n);  
        heap.makeHeap();  
        heap.sortHeap();  
    }  
      
    template <class ElemType,class Compare>  
    void MyPriorityQueue<ElemType,Compare>::push(const ElemType &val)  
    {  
        heap.setNumCounts(heap.getVec().size() - 1);//排除容器首部的哨兵元素  
        heap.makeHeap();  
        heap.pushHeap(val);  
        heap.sortHeap();  
    }  
      
    template <class ElemType,class Compare>  
    void MyPriorityQueue<ElemType,Compare>::pop()  
    {  
        heap.getVec().erase(heap.getVec().begin() + 1);//刪除佇列首部的元素  
        heap.setNumCounts(heap.getVec().size() - 1);//排除容器首部的哨兵元素  
        heap.makeHeap();  
        heap.sortHeap();  
    }  

PriorityQueueTest.cpp:
    #include "PriorityQueue.h"  
      
    #include <iostream>  
    #include <string>  
      
    using namespace std;  
      
    int main()  
    {  
        const int n = 9;  
        int data[n] = {0,1,2,3,4,8,9,3,5};  
        MyPriorityQueue<int> *priorityObj1 = new MyPriorityQueue<int>(data,n);  
        cout << "Current Heap: " << endl;  
        for (int i = 1;i <= priorityObj1->size();++i)  
        {  
            cout << priorityObj1->getHeap().getVec()[i] << " ";  
        }  
        cout << endl;  
        cout << "Size = " << priorityObj1->size() << endl;  
        cout << "Top element = " << priorityObj1->top() << endl;  
        priorityObj1->pop();  
        cout << "After pop one element:" << endl;  
        cout << "Size = " << priorityObj1->size() << endl;  
        cout << "Top element = " << priorityObj1->top() << endl;  
      
        cout << "Current Heap: " << endl;  
        for (int i = 1;i <= priorityObj1->size();++i)  
        {  
            cout << priorityObj1->getHeap().getVec()[i] << " ";  
        }  
        cout << endl;  
      
        priorityObj1->pop();  
        cout << "After pop one element:" << endl;  
        cout << "Size = " << priorityObj1->size() << endl;  
        cout << "Top element = " << priorityObj1->top() << endl;  
      
        cout << "Current Heap: " << endl;  
        for (int i = 1;i <= priorityObj1->size();++i)  
        {  
            cout << priorityObj1->getHeap().getVec()[i] << " ";  
        }  
        cout << endl;  
      
        priorityObj1->push(7);  
        cout << "After push one element 7:" << endl;  
        cout << "Size = " << priorityObj1->size() << endl;  
        cout << "Top element = " << priorityObj1->top() << endl;  
      
        cout << "Current Heap: " << endl;  
        for (int i = 1;i <= priorityObj1->size();++i)  
        {  
            cout << priorityObj1->getHeap().getVec()[i] << " ";  
        }  
        cout << endl;  
      
        delete priorityObj1;  
    }  
運算結果: