heap並不歸屬於STL容器組件,它是個幕後英雄,扮演priority queue
的助手,priority queue
允許用戶以任何次序將任何元素放入容器內,但是取出時一定是從優先級最高的元素開始取,heap
正是具有這樣的特性,適合作爲priority queue
的底層機制
heap的四種算法:push_heap
、pop_heap
、sort_heap
、make_heap
,對應插入、刪除、排序、建堆, 下述算法理解都以大頂堆爲例
由於堆是一棵完全二叉樹,所以可以很輕易地用一個數組存儲堆中的每一個元素,並且由子結點訪問到其父親結點和由父親結點訪問到其子結點。下面給出圖來說明該表示方法:
數據結構上heap的實現👇
STL的魅力之一在於能夠對特定類型的數據結構提供泛型化,並且提供高效的函數接口。STL不僅實現了heap算法,而且還彌補上述算法的不足。
平時的heap算法都是針對一個靜態數組,而STL以動態數組vector爲底層實現,但提供的依舊是建堆操作、插入操作、刪除操作、堆排序操作
//底層用靜態數組實現的heap
#include<iostream>
#include<algorithm>
#define maxn 1001 //heap's size
using namespace std;
struct Heap {
int size; // number of elements in array
int *array;
Heap() { //初始化
size = 0;
array = new int[maxn];
}
Heap(int n) { //init
size = 0;
array = new int[n];
}
~Heap() { //free memory
delete array;
}
bool empty() {
if(size != 0) return false;
return true;
}
int max() {
if(empty()) return -1;
return array[1];
}
};
而STL底層用vector實現
#include<vector>
#include<iostream>
#include<algorithm>//有heap算法
using namespace std;
int main()
{
int ia[9] = {0,1,2,3,4,5,6,7,8,9};
vector<int> ivec(ia, ia+9);//底層用vector
make_heap(ivec.begin(),ivec.end());
...
}
①數組新加入一個元素:先插入在數組的最後一個,在堆上看就是這棵完全二叉樹的底層最左邊葉子節點,要符合大頂堆,則不斷往上比較,如果當前index值大於父節點index/2則交換,
void insert(int value) {
array[++size] = value;
int index = size;
while(index > 1) {
if(array[index] > array[index/2]) swap(array[index],array[index/2]);
index /= 2;
}
}
而STL用push_heap
算法實現
inline void push_heap(b,e,cmp=greater<T>() )
向堆中插入元素分爲兩個步驟:
(1)先通過push_back
將待插入的元素插入到底層容器的末端
(2)再調用push_heap(b,e,cmp)函數堆新插入的元素做向上調整。
②刪除堆頂元素:將最後一個元素放到第一個元素處,再從上到下調整使符合大頂堆要求,
void del()
{
if(empty()) return;
swap(array[1],array[size--]);
int index = 1;
while(2*index <= size)
{
int next = 2*index;
if(next < size && array[next+1] > array[next]) next++;//和左右子節點中較大的比較交換
if(array[index] < array[next])
{
swap(array[index],array[next]);
index = next;//交換並往下比較,直到到葉子節點
} else break;
}
}
STL中的堆頂元素刪除操作:
堆假刪除算法:inline void pop_heap(b,e,cmp=greater() )
要實現堆的真正刪除操作,分兩步進行:
(1)先調用pop_heap
函數將首部的元素與尾部元素交換,再將原尾部的元素做向下調整操作。此時,原堆頂元素被放置在最後一個位置,並未從底層容器中刪除。
(2)若要實現真正的元素刪除,可以調用底層容器的pop_back
函數。
所以,在調用pop_heap函數後,若要實現元素真正從堆中刪除,還需要調用底層容器的pop_back函數。
③建堆:對非葉子節點進行進行調整從而得到一個大頂堆
void buildHeap(int array[],int size) {
int i,tmp,index;
for(i = size/2; i >= 1; i--) {//對每一個非葉子節點從上到下調整
tmp = array[i];
index = 2*i;
while(index <= size) {//堆化
if(index < size && array[index+1] > array[index]) index++;
if(array[index] < tmp) break;
array[index/2] = array[index];
index *= 2;
}
array[index/2] = tmp;
}
}
STL建堆算法:inline void make_heap( b, e , cmp=greater() )
該函數對[b,e)範圍中的元素建立一個堆,所建的堆的類型由cmp決定,默認爲大頂堆。
④堆排序:獲取堆頂元素, 與沒排序元素的最後一個交換, 當整個程序執行完畢就會得到一個遞增序列
void heapsort(int array[],int size)
{
buildheap(array, size);//先建堆得到一個合格的堆
int i = size;
while(true)
{
if(i<=1)
break;
swap(array, 1, i);//和最後一個交換
i--;//始終是未排序的最後一個
//heapify();再對前面的元素進行堆化
}
}
STL的堆排序算法:
inline void sort_heap(b,e,cmp=greater<T>() )
堆排序實際上是對堆中元素不斷地假刪除操作,只不過在刪除過程中,[b,e)中的e每刪除一次,就要做–e的更新。
小頂堆的測試程序
小頂堆取決於cmp
這個仿函數的定義
#include<iostream>
#include<vector>
#include<list>
#include<algorithm>//後面用到copy函數和heap相關函數
#include<iterator>//後面用到迭代器ostream_iterator
#include<functional>//後面用到了一個比較的仿函數greater<T>
using namespace std;
typedef vector<int> Vint;//vector底層
void print(const Vint& vec)//輸出當前vector容器中的元素
{
cout<<"容器內的元素爲:";
copy(vec.begin(),vec.end(),ostream_iterator<int>(cout," "));//將容器內的元素輸出到標準輸出設備上
cout<<endl;
cout<<"容器內元素的個數爲:"<<vec.size()<<endl<<endl;;
}
bool cmp(const int &a,const int &b)
{
return a>b;//大頂堆,則cmp相等於greater<int>(),注意不是greater<int>,前者是一個對象,後者是一個類
}
int main()
{
int arr[]={3,2,1,9,4,12,15,7};
vector<int>vec(arr,arr+sizeof(arr)/sizeof(int));//創建一個vector容器對象,將數組的副本壓入到該容器中
cout<<"-----------初始狀態---------------"<<endl;
print(vec);//將最初的vector容器的內容輸出
cout<<"-------------建堆----------------"<<endl;
make_heap(vec.begin(),vec.end(),cmp);//新建一個小頂堆
//⭐⭐上行代碼等價於make_heap(vec.begin(),vec.end(),greater<int>()⭐⭐
print(vec);
cout<<"----------彈出堆頂元素-----------"<<endl;
pop_heap(vec.begin(),vec.end(),cmp);//這裏也要加cmp,因爲彈出之後要給出向下調整的規則,否則系統會調用默認的最大堆調整方法
print(vec);
cout<<"--------向堆中插入值6的方法--------"<<endl;
vec.push_back(6);//先將待插入的值放在容器的末尾
push_heap(vec.begin(),vec.end(),cmp);//再最堆進行向下調整
print(vec);
cout<<"----------執行堆排序--------------"<<endl;
sort_heap(vec.begin(),vec.end(),cmp);
print(vec);
return 0;
}
優先隊列
需要調動到#include<queue>
,以下以a爲例:
大根堆的調用:priority_queue<int>a
;
小根堆的調用:priority_queue<int,vector<int>,greater<int> > a
;
a.size():返回堆內元素個數。
a.empty():如果堆爲空,返回真,否則返回假。
a.top():返回堆頂元素。
a.pop():刪除堆頂元素,自動整理。
a.push(x):插入一個元素x,自動整理。