1. 程式人生 > >內排序演算法-堆排序

內排序演算法-堆排序

堆排序,顧名思義,就是把待排序的資料按照一定的規則放到一個堆裡面去。不過,這裡這個堆不同於其他堆,這裡的堆是一顆完全二叉樹。那什麼是完全二叉樹呢,就是葉節點只能在最後一層或者倒數第二層,並且最後一層的結點都集中在該層最左邊的若干位置的二叉樹。

堆排序的基本思想就是構造一顆完全二叉樹,使得子節點的值均不大於(不小於)父節點,不大於對應大根堆,不小於對於小根堆。至於左右子節點的大小關係無所謂的啦。是不是有點抽象啦,咱們看圖講故事(此處我們使用大根堆)。

以陣列A為例,左側是沒有加任何約束構造出來的完全二叉樹,右邊是按照大根堆的規則調整得到的完全二叉樹。
示例

那如何將左側的完全二叉樹調整為右側的二叉樹呢?我們接著往下看。
按照從上往下的順序,依次比較每個父節點跟左右子節點的大小。當父節點的元素值小於左子節點的元素值或者右子節點的元素值時,將父節點的元素值與左右子節點較大的元素值進行交換。
交換規則

程式碼實現如下:

void percDown(int a[], int n)
{
    for(int i = 0; 2*i+1 < n; i++)
    {
        //存在左子節點
        if(a[i] < a[2*i+1])
        {
            swap(&a[i],&a[2*i+1]);
        }
        //存在右子節點
        if(a[i] < a[2*i+2] && 2*i+2 < n)
        {
            swap(&a[i],&a[2
*i+2]); } } }

通過第一輪的調整,較小的元素已經沉到底部,最後一層節點的值均小於父節點。重複該過程log2(n+1)次,n為總節點的個數,即陣列的長度(深度為k的完全二叉樹的節點個數為[2k1,2k1]),直至每一層節點均小於父節點。
這裡寫圖片描述

至此,陣列中的最大元素已經上浮至根節點。接下來,交換第一個元素跟最後一個元素的值,則陣列最後一個元素為當前最大的元素。(最後一個節點已經有序,後續討論不再包括該節點)
這裡寫圖片描述

對於剩餘的n1個節點,由於剛剛的交換破壞了原有的最大堆,因此重複之前的二叉樹調整操作,得到新的最大堆。(此處的調整隻需進行一輪即可)
這裡寫圖片描述

再次交換第一個節點和當前最後一個節點(即第n1個節點)的值,則倒數第二個節點歸位。
這裡寫圖片描述

重複上述過程,直至第一個節點歸位。

程式碼實現如下:

void output(int a[], int n)
{
    for(int i = 0; i < n; i++)
    {
        cout<<a[i]<<'\t';
    }
    cout<<endl;
}

void swap(int *x, int *y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

void heapSort(int a[], int n)
{
    // 重複若干輪構造初始最大堆
    for(int i = 0; i < log(n+1)/log(2)-1; i++)
    {
        percDown(a, n);
        output(a,n);
    }
    // 依次將最大元素歸位,重新調整最大堆
    for(int i = n-1; i >= 0; i--)
    {
        swap(a[0], a[i]);
        percDown(a, i);
        output(a,n);
    }
}