排序--最大堆構造和堆排序(單步查看過程)


這里先簡單說下最大堆的基本性質:

  • 最大堆一定是完全二叉樹
  • 當父節點為 n 時,左孩子為 n * 2 + 1,右孩子為 n * 2 + 2
  • 當孩子為 n 時,其父節點為: (n - 1) / 2 ----> 這一點很重要,在后面初始化的時候會用到
  • 父節點大於等於左孩子和右孩子,但左孩子不一定大於右孩子

了解以上基本性質之后,就可以先看一下如何對一個序列做最大堆的初始化。


最大堆的構造

思路:過程就像冒泡一樣,從最序號最大的父節點開始,查看是否滿足最大堆,如果不滿足,則調整(調整之后,還要查看被調整的節點是否依然滿足最大堆性質,如果不滿足,則需要往下遍歷調整,這部分在后面的舉例中會有說明),如果滿足,則繼續查看前一個父節點是否滿足,直接最終的0節點。

例如:這里有個數組:x[] = {2 5 3 9 7 1 6},則對應樹為:

該序列長度為7,最大下標為6,則最大的父節點下標就是: (6 - 1)/ 2 是2(基本性質第三條),對應的數值是3,
他的左孩子是1,右孩子是6,右孩子比父節點大,所以應該調整一下這兩個節點,得到:

該節點調整完之后,再查看前一個父節點,下標為1,對應的數值為5,
他的左孩子是9,右孩子是7,不滿足,所以父節點應該與左孩子進行交換,得到:

繼續往前,再前面一個父節點下標為0,數值為2,左孩子是9,右孩子是6,不滿足最大堆,父節點與左孩子交換,得到:

交換之前,左孩子為9,現在左孩子為2,導致這個左孩子不滿足最大堆性質,因為這個左孩子的左孩子大於左孩子,所以,這里就出現了上面括號中所說的:調整完之后,還要查看被調整的節點是否依然滿足最大堆的性質。
這里還要對調整之后的節點繼續調整:

至此,一個最大堆就初始化完成了!

堆排序

其實,明白了最大堆怎么構造出來的之后,堆排序就很容易了。
想一想,最大堆構造出來之后,其實就直接得到了最大值:x[0],
如果把 x[0] 與最后一個數字交換一下,然后對剩下的數字重新按之前的方法構造一下,不就找到了第二大的數字了嗎?
此時第二大的數字就是x[0],把它與剛剛參加排序的最后的一個數字交換一下,然后再對剩下的數字排序一下,就可以得到第三大的數字,
這么一直循環,就可以把當前數組排序完成了。
接着剛剛的那個例題,先把9與參與排序的最后一個數字對換,得到:

此時參與排序的,就只有:3,7,6,5,2,1。
因為x[0]被調整了,所以要查看x[0]是否依然最大堆性質,顯然是不滿足的,所以繼續調整x[0],得到:

x[0]與x[1]互換之后,導致被調整的x[1]又不滿足最大堆,那就再調整一下:

現在整個樹都滿足最大堆了,也就得到了現在參與排序的最大值x[0]為7,
所以,x[0]與當前參與排序的最后一位交換,得到:

此時參與排序的,只有:1,5,6,3,2。
按照上面步驟再次循環,這里就不寫了,直接放圖:

上代碼:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 打印數組
void print(int *array, int len)
{
    for (int i=0; i<len; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

// 交換兩個數值
void swap(int *array, int i, int j)
{
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
    return;
}

// 對當前父節點進行排序
// 查看該父節點是否滿足最大堆,如果不滿足則調整子節點
void sort (int *array, int father, int len)
{
    for (int lchild =father*2+1; lchild<len; lchild=father*2+1)
    {
        int k = lchild;  // 先用k指向左孩子
        int rchild = lchild + 1;
        if ((rchild < len) && (array[rchild] > array[lchild])) 
        {
            k = rchild;   // 如果有右孩子,且右孩子比左孩子還大,則更新k
        }

        // 這里的k,指向了左右孩子中較大的那個
        if (array[k] > array[father])
        {
            swap(array, k, father); // 交換父親和孩子的數值

            father = k;  // 這里就是查看被調整之后的節點k,是否依然滿足最大堆
        }
        else
        {
            break;  // 當前節點不需要被調整
        }
    }
    return;
}

int main(void)
{
    int x[] = {2,5,3,9,7,1,6};
    int len = sizeof(x)/sizeof(int);

    print(x, len); // 先輸出原始序列

    // 最大子節點下標為len-1,所以它的父節點是 (len-1-1) / 2
    for (int i = (len - 2)/2; i>=0; i--)
    {
        sort(x, i, len);
    }

    print(x, len); // 輸出初始化之后的最大堆

    for (int i=(len-1); i>0; i--)
    {
        swap(x, 0, i);  // 把最大的一個值放到末尾,然后對剩余的數組進行排序
        sort(x, 0, i);
    }
    print(x, len); // 輸出排序之后的序列
    return 0;
}

最終輸出為:
2 5 3 9 7 1 6 
9 7 6 5 2 1 3 
1 2 3 5 6 7 9 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM