1. 程式人生 > >排序——堆排序算法

排序——堆排序算法

uil 保存 初始化 adjust ges 二叉 分享 title 數據結構

堆排序利用的完全二叉樹這種數據結構所設計的一種算法,不過也是選擇排序的一種。

堆實質上是滿足如下性質的完全二叉樹:k[i]<=k[2*i]&&k[i]<=k[2*i+1]或者k[i]>=k[2*i]&&k[i]>=k[2*i+1],

樹中任一非葉子結點的關鍵字均不大於(或不小於)其左右孩子(若存在)結點的關鍵字。

技術分享

堆分大頂堆和小頂堆:k[i]<=k[2*i]&&k[i]<=k[2*i+1]是小頂堆,k[i]>=k[2*i]&&k[i]>=k[2*i+1]是大頂堆。堆排序利用了大頂堆(或小頂堆)堆頂記錄的關鍵字最大(或最小)這一特征,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單。

堆排序的實現:首先,初始化堆,是對所有的非葉子結點進行篩選。最後一個非葉子節點元素的下標是[n/2]向下取最大值,所以篩選只需要從第[n/2]向下取整個元素開始,從後往前進行調整。然後從最後一個非葉子節點開始,每次都是從父節點、左孩子、右孩子中進行比較交換,交換可能會引起孩子節點不滿足堆的性質,所以每次交換之後需要重新對被交換的孩子將節點進行調整。有了初始堆之後就可以進行排序了。

  堆排序是一種選擇排序。建立的初始堆為初始的無序區。

  排序開始,首先輸出堆頂元素(因為它是最值),將堆頂元素和最後一個元素交換,這樣,第n個位置(即最後一個位置)作為有序區,前n-1個位置仍是無序區,對無序區進行調整,得到堆之後,再交換堆頂和最後一個元素,這樣有序區長度變為2。

  不斷進行此操作,將剩下的元素重新調整為堆,然後輸出堆頂元素到有序區。每次交換都導致無序區-1,有序區+1。不斷重復此過程直到有序區長度增長為n-1,排序完成。

舉例:一個10位數的數列[5 4 3 1 2 9 7 8 0 6]

技術分享技術分享技術分享

按照這樣步驟,循環往復,即每次調整都是從父節點、左孩子節點、右孩子節點三者中選擇最大者跟父節點進行交換(交換之後可能造成被交換的孩子節點不滿足堆的性質,因此每次交換之後要重新對被交換的孩子節點進行調整)。有了初始堆之後就可以進行排序了。

從上述過程可知,堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,為了從R[1...n]中選擇最大記錄,需比較n-1次,然後從R[1...n-2]中選擇最大記錄需比較n-2次。事實上這n-2次比較中有很多已經在前面的n-1次比較中已經做過,而樹形選擇排序恰好利用樹形的特點保存了部分前面的比較結果,因此可以減少比較次數。對於n個關鍵字序列,最壞情況下每個節點需比較log2(n)次,因此其最壞情況下時間復雜度為nlogn。堆排序為不穩定排序,不適合記錄較少的排序。

代碼(C語言):

void HeapAdjust(int *a,int i,int size)
{
    int lchild=2*i;       
    int rchild=2*i+1;     
    int max=i;            
    if(i<=size/2)         
    {
        if(lchild<=size&&a[lchild]>a[max])
        {
            max=lchild;
        }
        if(rchild<=size&&a[rchild]>a[max])
        {
            max=rchild;
        }
        if(max!=i)
        {
            swap(a[i],a[max]);
            HeapAdjust(a,max,size);
        }
    }
}
void HeapSort(int *a,int size)  
{
    int i;
    for(i=size/2; i>=1; i--){
        HeapAdjust(a,i,size);
    }
    for(i=size;i>=1;i--)
    {
        //cout<<a[1]<<" ";
        swap(a[1],a[i]);           
          //BuildHeap(a,i-1);      
          HeapAdjust(a,1,i-1);     
    }
}

排序——堆排序算法