堆排序

本博客轉自:https://www.cnblogs.com/lanhaicode/p/10546257.html 非CSDN博客,轉載只爲方便查看。

堆排序

之前的隨筆寫了棧(順序棧鏈式棧)、隊列(循環隊列鏈式隊列)、鏈表二叉樹,這次隨筆來寫堆

1、什麼是堆?

堆是一種非線性結構,(本篇隨筆主要分析堆的數組實現)可以把堆看作一個數組,也可以被看作一個完全二叉樹,通俗來講堆其實就是利用完全二叉樹的結構來維護的一維數組

按照堆的特點可以把堆分爲大頂堆小頂堆

大頂堆:每個結點的值都大於等於其左右孩子結點的值

小頂堆:每個結點的值都小於等於其左右孩子結點的值

(堆的這種特性非常的有用,堆常常被當做優先隊列使用,因爲可以快速的訪問到“最重要”的元素)

 

2、堆的特點(數組實現)

 

(圖片來源:https://www.cnblogs.com/chengxiao/p/6129630.html)

我們對堆中的結點按層進行編號,將這種邏輯結構映射到數組中就是下面這個樣子

(圖片來源:https://www.cnblogs.com/chengxiao/p/6129630.html)

 

我們用簡單的公式來描述一下堆的定義就是:(讀者可以對照上圖的數組來理解下面兩個公式)

大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 

小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] 

 

3、堆和普通樹的區別

內存佔用:

普通樹佔用的內存空間比它們存儲的數據要多。你必須爲節點對象以及左/右子節點指針分配額外的內存。堆僅僅使用數組,且不使用指針

(可以使用普通樹來模擬堆,但空間浪費比較大,不太建議這麼做)

平衡

二叉搜索樹必須是“平衡”的情況下,其大部分操作的複雜度才能達到O(nlog2n)。你可以按任意順序位置插入/刪除數據,或者使用 AVL 樹或者紅黑樹,但是在堆中實際上不需要整棵樹都是有序的。我們只需要滿足對屬性即可,所以在堆中平衡不是問題。因爲堆中數據的組織方式可以保證O(nlog2n) 的性能

搜索:

在二叉樹中搜索會很快,但是在堆中搜索會很慢。在堆中搜索不是第一優先級,因爲使用堆的目的是將最大(或者最小)的節點放在最前面,從而快速的進行相關插入、刪除操作

 

4、堆排序的過程

先了解下堆排序的基本思想:

將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就爲最大值。然後將剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值,

如此反覆執行,便能得到一個有序序列了,建立最大堆時是從最後一個非葉子節點開始從下往上調整的(這句話可能不好太理解),下面會舉一個例子來理解堆排序的基本思想

 

給一個無序序列如下

int a[6] = {7, 3, 8, 5, 1, 2};

現在可以根據數組將完全二叉樹還原出來

好了,現在我們要做的事情就是要把7,3,8,5,1,2變成一個有序的序列,如果想要升序就是1,2,3,5,7,8   如果想要降序就是8,7,5,3,2,1 ,這兩種就是我們要的最終結果,然後我們就可以根據我們想要的結果來選擇

適合類型的堆來進行排序

升序----使用大頂堆

降序----使用小頂堆

5、爲什麼升序要用大頂堆呢

上面提到過大頂堆的特點:每個結點的值都大於等於其左右孩子結點的值,我們把大頂堆構建完畢後根節點的值一定是最大的,然後把根節點的和最後一個元素(也可以說最後一個節點)交換位置,那麼末尾元素此時就是最大元素了(理解這點很重要)

知道了堆排序的原理下面就可以來操作了,在進行操作前先理清一下步驟

 

(假設我們想要升序的排列)

第一步:先n個元素的無序序列,構建成大頂堆

第二步:將根節點與最後一個元素交換位置,(將最大元素"沉"到數組末端

第三步:交換過後可能不再滿足大頂堆的條件,所以需要將剩下的n-1個元素重新構建成大頂堆

第四步:重複第二步、第三步直到整個數組排序完成

 

6、圖解交換過程(得到升序序列,使用大頂堆來調整)

這裏以int a[6] = {7, 3, 8, 5, 1, 2}爲例子

先要找到最後一個非葉子節點,數組的長度爲6,那麼最後一個非葉子節點就是:長度/2-1,也就是6/2-1=2,然後下一步就是比較該節點值和它的子樹值,如果該節點小於其左\右子樹的值就交換(意思就是將最大的值放到該節點)

8只有一個左子樹,左子樹的值爲2,8>2不需要調整

 

 

下一步,繼續找到下一個非葉子節點(其實就是當前座標-1就行了),該節點的值爲3小於其左子樹的值,交換值,交換後該節點值爲5,大於其右子樹的值,不需要交換

下一步,繼續找到下一個非葉子節點,該節點的值爲7,大於其左子樹的值,不需要交換,再看右子樹,該節點的值小於右子樹的值,需要交換值

下一步,檢查調整後的子樹,是否滿足大頂堆性質,如果不滿足則繼續調整(這裏因爲只將右子樹的值與根節點互換,只需要檢查右子樹是否滿足,而8>2剛好滿足大頂堆的性質,就不需要調整了,

如果運氣不好整個數的根節點的值是1,那麼就還需要調整右子樹)

 

到這裏大頂堆的構建就算完成了,然後下一步交換根節點(8)與最後一個元素(2)交換位置(將最大元素"沉"到數組末端),此時最大的元素就歸位了,然後對剩下的5個元素重複上面的操作

(這裏用粉紅色來表示已經歸位的元素)

剩下只有5個元素,最後一個非葉子節點是5/2-1=1,該節點的值(5)大於左子樹的值(3)也大於右子樹的值(1),滿足大頂堆性質不需要交換

找到下一個非葉子節點,該節點的值(2)小於左子樹的值(5),交換值,交換後左子樹不再滿足大頂堆的性質再調整左子樹,左子樹滿足要求後再返回去看根節點,根節點的值(5)小於右子樹的值(7),再次交換值

 

得到新的大頂堆,如下圖,再把根節點的值(7)與當前數組最後一個元素值(1)交換,再重構大頂堆->交換值->重構大頂堆->交換值····,直到整個數組都變成有序序列

 

最後得到的升序序列如下圖

C++代碼實現:

#include <stdio.h>

void Swap(int *heap, int len);        /* 交換根節點和數組末尾元素的值 */
void BuildMaxHeap(int *heap, int len);/* 構建大頂堆 */

int main()
{
    int a[6] = {7, 3, 8, 5, 1, 2};
    int len = 6;    /* 數組長度 */
    int i;

    for (i = len; i > 0; i--)
    {
        BuildMaxHeap(a, i);
        Swap(a, i);
    }
    for (i = 0; i < len; i++)
    {
        printf("%d ", a[i]);
    }

    return 0;
}
/* Function: 構建大頂堆 */
void BuildMaxHeap(int *heap, int len)
{
    int i;
    int temp;

    for (i = len/2-1; i >= 0; i--)
    {
        if ((2*i+1) < len && heap[i] < heap[2*i+1])    /* 根節點大於左子樹 */
        {
            temp = heap[i];
            heap[i] = heap[2*i+1];
            heap[2*i+1] = temp;
            /* 檢查交換後的左子樹是否滿足大頂堆性質 如果不滿足 則重新調整子樹結構 */
            if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
            {
                BuildMaxHeap(heap, len);
            }
        }
        if ((2*i+2) < len && heap[i] < heap[2*i+2])    /* 根節點大於右子樹 */
        {
            temp = heap[i];
            heap[i] = heap[2*i+2];
            heap[2*i+2] = temp;
            /* 檢查交換後的右子樹是否滿足大頂堆性質 如果不滿足 則重新調整子樹結構 */
            if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2]))
            {
                BuildMaxHeap(heap, len);
            }
        }
    }
}

/* Function: 交換交換根節點和數組末尾元素的值*/
void Swap(int *heap, int len)
{
    int temp;

    temp = heap[0];
    heap[0] = heap[len-1];
    heap[len-1] = temp;
}

 

發佈了31 篇原創文章 · 獲贊 6 · 訪問量 2804
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章