前言
堆(二叉堆),一種動態的樹型結構,一種除了底層外,完全被填滿的二叉樹結構。因此,堆一般是基于數組去實現的,它不會出現數組中很多空缺的現象,而造成空間浪費。如下是一個完全二叉樹:
它可以用數組表示為[10,7,2,5,1]
,若以k表示當前數組的索引,那么:
- 其父節點:
floor((k-1)/2)
- 其左孩子:
2k+1
- 其又孩子:
2k+2
結合上圖,堆的性質如下:
- 堆必須是完全二叉樹;
- 任一節點要么比其子樹節點大,要么小;
- 根據上面性質,堆被分為最大堆(大頂堆)和最小堆(小頂堆)。
堆的主要用途:
- 構建優先隊列;
- 支持堆排序;
- 快速找出集合中的最大值或最小值。
堆結構的基本操作(以最大堆為例,本文均使用vector容器存儲數組,假設vector容器的基本操作時間復雜度為Θ(1)):
-
MaxHeap
:維護最大堆的性質,時間復雜度O(nlgn); -
BuildHeap
:從無序的輸入數據構造一個最大堆,時間復雜度為O(n);
堆結構的基本操作
1.MaxHeap:
輸入一個數組A和一個下標i,使不滿足最大堆性質的A[i]逐級下降,直到滿足。
void MaxHeap(vector<int> &A, int i){
int max; //存儲父節點和其子樹節點中的最大值下標
int lef_child = 2i; //左孩子
int rig_child = 2i + 1; //右孩子
//左孩子大于父節點
if(lef_child <= A.size() && A[lef_child] > A[i])
max = lef_child;
else max = i;
//右孩子更大
if(rig_child <= A.size() && A[rig_child] > A[max])
max = rig_child;
//將最大值上移至父節點
if(max != i){
//交換
int temp = A[i]; A[i] = A[max]; A[max] = temp;
//更新了數組,需繼續查看當前元素是否滿足最大堆性質
MaxHeap(A, max);
}
}
因為每一個節點的子樹節點數(包括孩子的孩子)至多為2n/3(n是整個樹的節點數,最壞情況即節點為根節點,且底層大于等于半滿),所以該算法的時間復雜度為T(n) <= T(2n/3) + Θ(1)
通過主方法求解得T(n)=O(lgn)
,最后因為含n個元素的堆高為O(lgn),所以其時間復雜度又可以表示為O(h)
。
2.BuildHeap:
可以通過自底向上的方法,從最后一個葉節點開始倒序遍歷,并調用MaxHeap判斷當前節點子樹是否滿足最大堆性質。
void BuildHeap(vector<int> &A){
int i = A.size()/2; //最后一個非葉節點的位置
for(i; i >= 0; i--) //自底向上維護
MaxHeap(A, i);
}
該算法的時間復雜度很容易通過,循環n次,每次調用MaxHeap耗費O(lgn),從而得出T(n)=O(nlgn)
,雖然正確,但是該算法上界還可以繼續緊確。
一個含n個元素的堆高(最底層高為0)為floor(lgn)
,而該堆最多包含ceil(n/2^(h+1))
個高度為h的節點。而一個高度為h的節點運行MaxHeap的時間復雜度為O(h),所以可以將BuildHeap的總代價表示為:
構建一個優先隊列
1.什么是優先隊列
優先隊列是一種特殊的隊列,它不按先進先出的原則,而是以優先度來彈出元素。它本質是一種用來維護由一組元素構成的集合S的數據結構,其中每個元素都有一個相關的值,稱為關鍵字。和堆一樣,優先隊列也分為最大優先隊列和最小優先隊列。
2.優先隊列的相關操作
HeapTop: 返回并刪除掉當前堆頂,時間復雜度為O(lgn)。
int HeapTop(vector<int> &A){
if(A.size() < 1) exit(0); //沒有元素
int max = A[0];
//堆頂等于最后一個值,并將最后一個值彈出
A[0] = A[A.size()-1];
A.push_pop();
//維護最大堆性質
MaxHeap(A, 0);
return max;
}
HeapInsert:插入一個元素到當前堆中,時間復雜度為O(lgn)。
void HeapInsert(vector<int> &A, k){
A.push_back(A[0]);
A[0] = k;
//維護最大堆性質
MaxHeap(A, 0);
}
STL中堆與優先隊列的實現
1.堆
heap不屬于STL中的容器組件,它是以算法的形式呈現,“默默扮演著幕后英雄”。heap默認是最大堆排序。使用方法如下:
vector<int> ivec{0, 1, 2};
//最大堆[2, 1, 0]
make_heap(ivec.begin(), ivec.end());
ivec.push_back(5);
//在堆的基礎上進行數據插入[5, 2, 0, 1]
push_heap(ivec.begin(), ivec.end());
//pop_heap并沒有刪除元素,而是將堆頂元素與最后一個元素進行了替換
//[2, 1, 0, 5]
pop_heap(ivec.begin(), ivec.end());
//刪除堆頂的元素[2, 1, 0]
ivec.pop_back();
//默認小堆排序[0, 1, 2]
sort_heap(ivec.begin(), ivec.end());
2.優先隊列
priority_queue是STL中優先隊列的名稱,默認也是最大堆排序。使用方法如下:
priority_queue<int> que;
que.push(x); //每次push之后會自動維護,保證堆頂是優先級別最高的
que.pop();
que.top();
priority_queue還可以自定義存儲的數據類型以及排序方式,具體應用可以【關注公眾號DoCode,每日一道LeetCode,將零碎時間利用起來】回復關鍵字“堆”查看。