八大排序算法——堆排序

八大排序

排序,分爲內部排序和外部排序,內部排序是指將數據記錄在內存中進行排序,而外部排序因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存,所以稱之爲外部排序,我們這裏講的八大排序全部屬於內部排序。

這裏寫圖片描述

八大排序時間/空間複雜度及穩定性

這裏寫圖片描述

堆排序

堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分爲大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在數組的非降序排序中,需要使用的就是大根堆,因爲根據大根堆的要求可知,最大的值一定在堆頂。這裏要注意的是堆排序採用的是完全二叉樹的思想,但它的存儲結構採用的是數組。

堆排序的過程(大根堆)

  • 調整堆:調整堆即是將最初無規則的堆調整爲大根堆。它的每一次調整都是將非葉子結點i與其左孩子left[i]和右孩子light[i]進行比較,若非葉子結點i不是其中最大的,那麼將其與最大的結點位置互換。堆的第一次調整是從最後一個非葉子結點開始依次向根節點調整的,這就是爲什麼它要採用完全二叉樹的原因,因爲這樣可以快速的在數組中定位最後一個非葉子結點,即a[n/2-1],其中a是待排序的數組,n是數組a的下標值。那麼就是說,下標爲n/2-1的結點就是最後一個非葉子節點。堆的第一次調整函數的調用關係大概可以總結爲循環調用+遞歸調用。其過程如下:
    • 初始完全二叉樹
      初始完全二叉樹
    • 數值3爲最後一個非葉子結點,將其與它的孩子結點相比較,8>3,位置互換
      這裏寫圖片描述
    • 判斷被調整後的結點3是否爲非葉子結點,3不是,函數調用進入下一層循環,將結點7與它的左右兒子結點相比較,20>17>7,將結點20與結點7位置互換
      這裏寫圖片描述
    • 判斷被調整後的結點7是否爲非葉子結點,7不是,函數調用進入最後一層循環,將結點16與它的左右兒子結點相比較,20>16>7,將結點16與結點20位置互換
      這裏寫圖片描述
    • 判斷被調整後的結點16是否爲非葉子結點,16是,函數進行遞歸調用,將結點16與它的左右兒子結點相比較,17>16>7,將結點17與結點16位置互換
      這裏寫圖片描述
    • 判斷被調整後的結點16是否爲非葉子結點,16不是,循環結束,堆調整完畢。
  • 堆排序:可以看到,上面的調整堆算法循環調用執行完畢以後,結果是一個大根堆,堆排序所要做的就是將堆頂元素20與最後一個元素3互換位置,即將a[0]與a[n-1]互換位置,換位置之後a[0]至a[n-2]元素又組成一個新的堆,這時的調整堆與第一次調整剛好相反,它是從堆頂元素開始調整,一直往下延伸,是一個純遞歸的過程,具體過程如下:
    這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述
    到這裏,排序就算是執行完了,從上述過程可知,堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,爲了從a[1…n]中選擇最大記錄,需比較n-1次,然後從a[1…n-2]中選擇最大記錄需比較n-2次。事實上這n-2次比較中有很多已經在前面的n-1次比較中已經做過,而樹形選擇排序恰好利用樹形的特點保存了部分前面的比較結果,因此可以減少比較次數,這就是爲什麼第一次調整堆是從下往上,而之後都是從上往下。對於n個關鍵字序列,最壞情況下每個節點需比較log2(n)次,因此其最壞情況下時間複雜度爲nlogn。堆排序爲不穩定排序,不適合記錄較少的排序。下面是堆排序的c語言實現:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>

//堆調整算法
void HeapAdjust(int num[],int i,int length)
{
    //定義max保存以num[i]爲根的最小堆中最大值的下標,初始值爲i
    int max=i;
    //判斷num[i]是否爲根結點 (下標i<=length/2-1的結點都是根節點)
    if(i<=length/2-1)
    {
        //判斷num[2*i+1]是否在堆內,若在,判斷其是否大於num[max],若大於,將其下標2*i+1賦予給max
        if((2*i+1)<length && num[2*i+1]>num[max])
        {
            max=2*i+1;
        }
        //判斷num[2*(i+1)]是否在堆內,若在,判斷其是否大於num[max],若大於,將其下標2*(i+1)賦予給max
        if((2*(i+1))<length && num[2*(i+1)]>num[max])
        {
            max=2*(i+1);
        }
        //若i不等於max,則說明以num[i]爲根的最小堆中num[max]是最大值,將num[max]與根結點num[i]交換位置
        if(i!=max)
        {
            num[i]=num[i]^num[max];
            num[max]=num[i]^num[max];
            num[i]=num[i]^num[max];
            //交換後,爲防止以num[max]爲根結點的最小堆結構發生變換,再次調用堆調整算法
            HeapAdjust(num,max,length);
        }
    }
}
//堆排序算法
void HeapSort(int num[],int length)
{
    int i;
    //找到最後一個非葉子結點,先從最後一個非葉子結點組成的最小堆進行堆調整
    for(i=length/2-1;i>=0;i--)
        //調用調整堆算法將數組從下往上調整爲大根堆
        HeapAdjust(num,i,length);
    for(i=length-1;i>0;i--)
    {
        //將數組第一個數和最後一個數換位置
        num[i]=num[i]^num[0];
        num[0]=num[i]^num[0];
        num[i]=num[i]^num[0];
        //繼續調用調整堆算法,將剩餘數據組成的堆從上往下調整爲大根堆
        HeapAdjust(num,0,i);
    }
}
int main()
{
    int i;
    int num[]={8,5,7,12,48,36,4};
    HeapSort(num,sizeof(num)/sizeof(int));
    for(i=0;i<sizeof(num)/sizeof(int);i++)
    {
        printf("%d\t",num[i]);
    }
    printf("\n");
}

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