(2)排序之堆排序

準備工作


1.堆的概念:

堆通常是一個可以被看做一棵樹的數組對象。在隊列中,調度程序反覆提取隊列中第一個作業並運行,因而實際情況中某些時間較短的任務將等待很長時間才能結束,或者某些不短小,但具有重要性的作業,同樣應當具有優先權。堆即爲解決此類問題設計的一種數據結構。(wiki上的定義)


在堆排序中,我們用到的是二叉堆(是一棵二叉樹的一種)。


2.最大堆(屬於二叉堆):

每個父結點的值都大於它的左右兩個子結點的值,在根節點存儲着堆裏所有數的最大值。


堆排序

1.堆修復:

假設現在我們已經得到了一個堆,這個堆除了根節點外均符合最大堆的定義。
   比如:            7(該點不滿足條件) 
                       /    \
                     8      5
                   /   \    /  \
                 6    2  1   3
               /   \

              0   4

此時我們要將其修復成一個最大堆,叫做堆修復。

設堆存在數組a[]中,下標從1開始,共有 n 個節點。

我們先比較一下根節點和其左右兒子的大小,找出最大的那個數的下標largest,有兩種情況:

(1)largest 不爲根節點(即是左右兒子中的一個)

         此時,root (根節點)和 a[largest]交換,之後對largest繼續修復(採用遞歸)。

(2)largest是根節點,此時表明修復堆已經完成。

這樣是不是堆就修復完了呢?  當然不是。

注意到我們如果修復到了葉子節點,此時該修復工作也完成了,所以當左右某個兒子的下標大於了n時,本次修復結束(我們是採用遞歸來修復的,所以本次修復結束不意味着總修復結束)。


接下來我們就可以用代碼實現了:

void repair_heap(int a[], int pos, int sz)
{
    int l = pos * 2;
    int r = pos * 2 + 1;
    int largest;

    if(l <= sz && a[l] > a[pos])
        largest = l;
    else largest = pos;

    if(r <= sz && a[r] > a[largest])
        largest = r;

    if(largest != pos)
    {
        swap(a[largest], a[pos]);
        repair_heap(a, largest, sz);
    }
}


不難看出,這一步的複雜度爲O(lgn).

sz(size)的作用在後面我們會看到。

2.建立最大堆:

建立最大堆我們也是採用的遞歸方式。建堆其實是個比較容易的過程,按數組元素的初始順序直接建立就好。
將堆修復成最大堆也不難:我們從葉子節點開始,逐一向上修復堆。

代碼實現:
void build_maxheap(int a[], int pos)
{
    int l = pos * 2;
    int r = pos * 2 + 1;
    if( l <= n )
        build_maxheap( a, l );
    if( r <= n)
        build_maxheap( a, r );
    repair_heap(a, pos, n);
}


這一步的複雜度爲 O(n).

3.排序:

我們建立了最大堆以後根節點的值就是最大值,我們將這個最大值存儲後排出堆,再找剩下樹的最大值,就可以得到第二大值,以此類推,我們將得到有序列。

怎麼實現將最大值存儲後再排出堆呢?

我們將第 n 個元素和 a[1] 交換,然後再讓堆的節點數減1(這個就通過sz來實現,我們儘量不改變n)。

接下來我們從a[ 1 ]開始修復堆就行了,以此循環,最終達到排序的效果。


代碼如下:

void heap_sort(int a[])
{
    int heapsize = n;
    while(heapsize > 1)
    {
        swap(a[heapsize--], a[1]);
        repair_heap(a, 1, heapsize);
    }
}
這一步複雜度爲 O(nlgn)。

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