二叉堆
[注] 本文以小根堆爲演示
存儲
二叉堆,本質上是一棵二叉樹——一棵完全二叉樹
完全二叉樹 是 效率很高的數據結構,完全二叉樹 是由 滿二叉樹 而引出來的。對於深度爲K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲完全二叉樹。
因爲有這個性質,,我們可以很方便的存儲一棵二叉堆。
假設存在一個數組array[],array[i]表示的是節點i存儲的數值。
此時我們令1爲這顆完全二叉樹的根節點,,,對於節點a來說, 是a的左兒子, 是a的右兒子(有疑問可以自己畫一棵二叉樹,標上序號)
堆性質
對於一個堆,這裏以小根堆爲例,大根堆相反
這個堆的任意一個節點的值都要小於等於這個節點的所有兒子節點
擴展一下可以得知,這個堆中某一棵子樹的根節點,是這棵子樹中所有節點中值最小的一個
然後如果我們要取得堆中所有元素的最小值,那麼只需要獲取一下array[1]即可
堆操作
一個二叉堆的操作,最常用的主要有三:
- 向堆中加入一個元素
- 取出堆中最小的那個元素
- 刪除堆中最小的元素
接下來我們看看如何完成這三個操作
實現
其實完成以上操作(主要是加入和刪除元素),其實都緊緊圍繞着一個東西:
維護堆性質
首先來看加入元素,因爲要加入的元素我們並不知道大小,所以從堆裏找一個元素進行替換這個做法明顯不可取,此時我們就要想一想極端的:
- 加入的這個數字比堆中任何數字都小
- 加入的這個數字比堆中任何數字都大
可以得知,如果以上條件我們的堆可以維護的話,那麼其他任何條件想維護起來都不困難了
所以,我們可以把這個數字放在Array數字的末尾後一位(此處的末尾是堆的末尾)
然後堆又是個完全二叉樹,所以此時這個新添加的節點算是這棵樹的葉子節點,,,好了,既然節點已經加入進來了,我們就要考慮如何維護堆性質了
此時該節點已經是葉子節點,所以不存在什麼兒子要大於父親(自己),此時這個節點的身份是上一層某個節點的兒子,所以我們要考慮的僅僅是該節點的父親是否小於該節點,如果不滿足堆性質則兩者交換值
【注】數組存儲的完全二叉樹中,兒子id = a,則其父親節點的id =
如果交換節點後,此時這個新節點就不是葉子節點了,而變成了它之前的父節點,然後我們還要繼續檢測堆性質是否滿足,而我們改變的僅僅是這個節點,所以繼續檢查這個節點就好了,,,接着剛纔,這個節點與父節點互換,所以此節點的兒子節點都已經滿足堆性質,所以我們繼續考慮這個節點的父節點即可,直到滿足堆性質或者這個節點已經成爲了堆的根。
代碼:(其實還是很簡短的)
void swap_ (int *a,int *b) {
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
void insert_ (int num) {
heap[++tail] = num;
int n = tail, p = n >> 1;
while (p && heap[p] > heap[n]) {
swap_(&heap[p],&heap[n]);
n = p, p = n >> 1; //利用二進制加速
}
}
接下來就取最小值操作了,,這個沒啥說的,返回array[1]就好
最後一個就是pop操作,也就是刪除最小值
根據堆性質,最小值一定就是節點1,也就是root節點,所以我們只需要”刪除”掉array[1]就好,但是如果真的刪除,那麼整棵樹都會炸掉。。
這樣想,我們只是刪除了root節點,而root的倆個兒子表示的子樹都分別滿足堆性質,完全可以用一個無窮大INF來替換掉root,表示刪除了root,然後我們在維護一下堆性質,可以知道,INF最終會移動到堆的葉子節點,,,堆的root則替換成了堆中的其他節點,而堆性質仍然滿足,,,
但是,這樣太麻煩了,因爲這裏是用數組存儲的堆,我們完全可以用堆數組中的最後一個元素替換掉root,然後將堆的大小size-1,表示刪除掉root節點,然後對root維護堆性質。
具體操作:
因爲堆性質是父親節點要小於任意兒子,所以我們取兩個兒子中更小的那個和root比較
如果不滿足堆性質,則交換變量,滿足則結束
如果交換了變量,則該節點上方的節點都滿足了堆性質,所以我們考慮下面就好,繼續比較該變量與其子兒子,直到滿足堆性質或者到達堆的尾部。
代碼:
void pop () {
heap[1] = heap[tail--];
int p = 1, son = p << 1;
while (son <= tail) {
if (son < tail && heap[son] > heap[son + 1]) son++;
if (heap[p] <= heap[son]) break;
swap_(&heap[p], &heap[son]);
p = son,son = p << 1;
}
}
完整代碼
最後貼完整代碼
#include <stdio.h>
int heap [10000],tail;
void swap_ (int *a,int *b) {///½»»»±äÁ¿
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
void insert_ (int num) {
heap[++tail] = num;
int n = tail, p = n >> 1;
while (p && heap[p] > heap[n]) {
swap_(&heap[p],&heap[n]);
n = p, p = n >> 1;
}
}
int top() {
if (tail) return heap[1];
return 0;
}
void pop () {
heap[1] = heap[tail--];
int p = 1, son = p << 1;
while (son <= tail) {
if (son < tail && heap[son] > heap[son + 1]) son++;
if (heap[p] <= heap[son]) break;
swap_(&heap[p], &heap[son]);
p = son,son = p << 1;
}
}
int main () {
insert_(3);
insert_(2);
insert_(1);
insert_(4);
printf ("%d\n", top());
pop ();
printf ("%d\n", top());
pop ();
printf ("%d\n", top());
pop ();
printf ("%d\n", top());
pop ();
printf ("%d\n", top());
pop ();
printf ("%d\n", top());
return 0;
}
輸出:
1
2
3
4
0
0