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++選手來說,這幾個演算法函式很有用,詳細解釋可以參見:
下面的_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;
}
運算結果: