文章首發於微信公眾號:小K演算法,關注第一時間獲取更新資訊
1 演進
結點和邊,構成一個圖。
不含環的連通圖,便成了一棵樹。每個結點擁有的子結點數稱為結點的度。
多棵樹便構成了一個森林。
結點的度最大為2的樹便是二叉樹;最大度為N的是N叉樹,或多叉樹。
除葉子結點,每個結點的度都為2,稱為滿二叉樹。
除去最後一層之後的子樹為滿二叉樹,且最後一層結點依次從左到右分佈,則稱為完全二叉樹。
如果在完全二叉樹上再加一個限制條件:如結點都大於等於其子結點,或者小於等於其子結點,則稱為堆。
每個結點都大於等於其子結點,稱為大根堆。
每個結點都小於等於其子結點,稱為小根堆。
2 堆儲存
2.1 順序儲存:陣列
用陣列儲存,將一個線性陣列對映成一棵完全二叉樹,父結點為i,則左兒子為2i+1,右兒子為2i+2。
程式碼如下
int heap[10];
2.2 鏈式儲存:連結串列
定義一個結點的結構體,兩個指標分別指向左兒子和右兒子。
程式碼如下
struct Node {
int value;
Node *lson, *rson;
};
Node *heap;
以下思想都以大根堆舉例。
3 堆調整
3.1 向上調整
子結點與父結點的下標關係如下:
用一個指標指向待調整的結點:
- 先比較是否大於父結點,如果大於就進行交換,並將指標上移到父結點
直到指向根結點或者當前結點小於等於父結點。
程式碼實現
//將heap[k]向上調整
int heapUp(int *heap, int k) {
int parent, son, x;
x = heap[k];
son = k;
parent = (son - 1) / 2;
while (son > 0) {
//如果父結點大於等於heap[k]則退出,否則將父結點下移
if (heap[parent] >= x)
break;
heap[son] = heap[parent];
son = parent;
parent = (son - 1) / 2;
}
heap[son] = x;
return 0;
}
3.2 向下調整
父結點與子結點的下標關係如下:
用一個指標指向待調整的結點:
- 先比較兩個子結點哪個更大,取出更大的子結點
- 再比較更大的子結點是否大於父結點,如果大於就進行交換,並將指標下移
直到指向葉子結點或者當前結點大於兩個子結點。
程式碼實現
//將heap[k]向下調整
int heapDown(int *heap, int k, int n) {
int parent, son, x;
x = heap[k];
parent = k;
son = 2 * k + 1; //左孩子結點
while (son <= n) {
//比較左右兒子,選擇較大的一個
if (son + 1 <= n && heap[son + 1] > heap[son])
son++; //使son指向左右孩子中較大的結點。
//如果兒子結點中較大的都小於等於待調整結點則退出,否則將子結點上移
if (heap[son] <= x)
break;
heap[parent] = heap[son];
parent = son;
son = 2 * parent + 1;
}
heap[parent] = x;
return 0;
}
4 增減元素
4.1 push
從堆尾插入元素,再對該元素進行向上調整直到滿足堆性質。
4.2 pop
將堆頂彈出,用堆尾的元素置換,再對堆頂的元素進行向下調整。
5 構建堆
5.1 插入構建
依次向堆尾插入元素,並對該元素進行向上調整,直到滿足堆性質。
時間複雜度:
插入一個元素要調整的高度為logi,所以插入n個元素的總次數為log1+log2+...+logn=log(n!)。
根據斯特林公式,有如下證明,所以複雜度O(nlogn)。
5.2 調整構建
待調整的陣列,可以直接看成是一棵完全二叉樹。
從(n-1)/2位置開始,將每個元素進行向下調整,直到根結點。對於每一個待調整的當前結點,下面的子樹都已經滿足堆性質,所以調整完所有結點便成了堆。
時間複雜度:
倒數第二層有2^(h-2)個結點,調整高度為1,依次類推,第一層有1個結點,調整高度為h-1,整體加起來的複雜度為O(n)。
程式碼實現
void buildHeap(int *heap, int n) {
for (int i = (n - 1) / 2; i >= 0; --i) {
heapDown(heap, i, n);
}
}
6 排序
一個已經調整完成的大根堆。
核心思想:
- 將堆頂與堆尾的元素置換
- 整體元素長度減1
- 對堆頂元素進行向下調整
重複以上過程直到整體元素為1,這時就變成了一個升序排列的陣列。
模擬過程:
Step 1
Step 2
7總結
堆排的複雜度為nlogn,應用場景很廣泛,這篇文章主要講清楚堆相關的操作,具體的應用和建模以後會再專門寫文章講解。
如果喜歡小K的文章,請點個關注,分享給更多的人,小K將持續更新,謝謝啦!
掃描下方二維碼關注公眾號:小K演算法,第一時間獲取更新資訊!