二叉堆詳解 及 C代碼實現

二叉堆

[注] 本文以小根堆爲演示

存儲

二叉堆,本質上是一棵二叉樹——一棵完全二叉樹

完全二叉樹 是 效率很高的數據結構,完全二叉樹 是由 滿二叉樹 而引出來的。對於深度爲K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲完全二叉樹。

因爲有這個性質,,我們可以很方便的存儲一棵二叉堆。

假設存在一個數組array[],array[i]表示的是節點i存儲的數值。

此時我們令1爲這顆完全二叉樹的根節點,,,對於節點a來說,a×2 是a的左兒子,a×2+1 是a的右兒子(有疑問可以自己畫一棵二叉樹,標上序號)

堆性質

對於一個堆,這裏以小根堆爲例,大根堆相反

這個堆的任意一個節點的值都要小於等於這個節點的所有兒子節點

擴展一下可以得知,這個堆中某一棵子樹的根節點,是這棵子樹中所有節點中值最小的一個

然後如果我們要取得堆中所有元素的最小值,那麼只需要獲取一下array[1]即可

堆操作

一個二叉堆的操作,最常用的主要有三:

  1. 向堆中加入一個元素
  2. 取出堆中最小的那個元素
  3. 刪除堆中最小的元素

接下來我們看看如何完成這三個操作

實現

其實完成以上操作(主要是加入和刪除元素),其實都緊緊圍繞着一個東西:

維護堆性質

首先來看加入元素,因爲要加入的元素我們並不知道大小,所以從堆裏找一個元素進行替換這個做法明顯不可取,此時我們就要想一想極端的:

  1. 加入的這個數字比堆中任何數字都小
  2. 加入的這個數字比堆中任何數字都大

可以得知,如果以上條件我們的堆可以維護的話,那麼其他任何條件想維護起來都不困難了

所以,我們可以把這個數字放在Array數字的末尾後一位(此處的末尾是堆的末尾)

然後堆又是個完全二叉樹,所以此時這個新添加的節點算是這棵樹的葉子節點,,,好了,既然節點已經加入進來了,我們就要考慮如何維護堆性質了

此時該節點已經是葉子節點,所以不存在什麼兒子要大於父親(自己),此時這個節點的身份是上一層某個節點的兒子,所以我們要考慮的僅僅是該節點的父親是否小於該節點,如果不滿足堆性質則兩者交換值

【注】數組存儲的完全二叉樹中,兒子id = a,則其父親節點的id = (int)(a÷2)

如果交換節點後,此時這個新節點就不是葉子節點了,而變成了它之前的父節點,然後我們還要繼續檢測堆性質是否滿足,而我們改變的僅僅是這個節點,所以繼續檢查這個節點就好了,,,接着剛纔,這個節點與父節點互換,所以此節點的兒子節點都已經滿足堆性質,所以我們繼續考慮這個節點的父節點即可,直到滿足堆性質或者這個節點已經成爲了堆的根。

代碼:(其實還是很簡短的)

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章