堆排序算法及其c語言泛型編程

一:什麼是堆?
1.堆: n個元素的序列{k1,k2,k3,.....kn}當且僅當滿足以下關係是,稱爲堆. {ki <= k2i 且 ki <= k2i+1} 或
{k2i <= ki 且 k2i+1 <= ki} (i = 1,2,...[n/2] ).這也是堆的一個性質.

2.堆結構是一種數組對象堆,它可以被視爲一棵完全二叉樹如下圖,  書中每個節點與數組中存放該節點中值的那個元素
對應,除了最後一層,其餘的每個節點都是滿的.因爲堆可看成是一棵完全二叉樹,那它就滿足一些樹的性質,
對於有n個結點的完全二叉樹,對任一結點i 有:  
  1)如果 i = 1,則結點i是二叉樹的根,無雙親;
    2)如果i > 1,則其雙親Parent(i)是[ i / 2];
    3)如果2i > n,則結點i 無左孩子(結點i爲葉子結點);否則,其左孩子Lift_child(i) 是結點2i.
    4)如果2i + 1 > n,則結點i無右孩子;否則,其右孩子right_child(i)是結點2i + 1.
    5)其非終端結點是第[ n / 2]個元素,(非終端結點是指:完全二叉樹中的最後一個帶有葉子的結點).

            

二:堆的分類
堆包含: 1.大根堆. 2.小根堆.       
        1. 大根堆必須滿足: {ki <= k2i , ki <= k2i+1}  (i = 1,2,...[n/2] ), 大根堆排序後元素是由小到大的順序.
        2.小根堆必須滿足:  {k2i <= ki , k2i+1 <= ki} (i = 1,2,...[n/2] ).  小根堆排序後元素是由大到小的順序.
【例】關鍵字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分別滿足堆性質(1)和(2),
故它們均是堆,其對應的完全二叉樹分別如小根堆示例和大根堆示例所示:



在下面所舉的例子或代碼中默認的是數組中元素是從下標1開始存儲的.

三:如何實現堆排序?
1.堆排序需要解決的兩個問題就是:(1)如何由一個無序序列建成一個堆? (2)如何在輸出堆頂元素之後,
   調整剩餘元素成爲一個新的堆?
      
2. 讓一個無序序列建成一個堆的最重要的步驟就是堆調整(牢記堆性質).
    堆調整的思想:
       1).把待調整的記錄當成根,然後比較此根和其左,右孩子這三個數的大小,如果左,右孩子中還中有一個最大的,
            則把此最大值與根值交換(這是大根堆調整法,小根堆調整隻需找到最小的值並與根交換就行),
       2).接着從最大值下標開始,並又以此結點爲根重複1)中的步驟直到最大值下標循環的葉子結點.
        3)如果1)中比較後得到的最大值(/最小值)仍爲根,則表示以此根結點的子序列已滿足堆性質,就已經是堆了,

下面就堆調整的僞代碼:
    array[]可知是我們將要調整的序列數組,index 則表示的是將要調整的記錄的下標.wait_adjust_length是待調整堆的長度.  

點擊(此處)摺疊或打開

  1. heap_adjust(NumType array[], int index ,int wait_adust_length)

  2.     lift_index = 2 * index ;//記錄待調整記錄的左孩子的下標
  3.     right_index = 2 * index + 1;//記錄待調整記錄的右孩子的下標
  4.    
  5.   if lift_index <</SPAN>= wait_adjust_lenght and array[lift_index] > array[right_index]
  6.           then max_num_index = lift_index;
  7.           else max_num_index = right_index;
  8.   if right_index <</SPAN>= wait_adjust_length and array[right_index] > array[max_num_index]
  9.    then max_num_index = right_index;
  10.    if max_num_index != index
  11.         then exchange array[index] <</SPAN>---> array[max_num_index]
  12.           heap_adjust(array, max_num_index);
 heap_adjust的作用過程如下圖:  此處wait_adjust_length = 10;
        圖a) 改堆的初始構造,在節點i =2處array[2]違反了堆的性質,因爲它不大於它的兩個子女(此處是以 大根堆 舉例).  
在圖b)中通過交換array[2]與array[4],因此在結點2處恢復的堆的性質,但又在結點4處違反了堆的性質現在遞歸調用heap_adjust(array, 4)就置index=4.
圖(c)中在交換了array[4]與array[9]後,結點,結點4處堆性質得到恢復,遞歸調用heap_adjust(array, 9)對該數據結構不再引起任何變化.        
    在算法的每一步裏,從元素array[i],array[lift_index]和array[right_index]中找到最大的,並將其下標存在max_num_index中.如果array[index]是最大的,則以index爲根的子樹已是堆,算法結束.否則,
index的某個子結點中有最大元素,則換array[index]和array[max_num_index],從而使i及其子女
滿足堆的性質,交換後的結點max_num_index中原先的array[index],以該結點爲根的子樹又有可能
 違反堆性質,因而,要對該子樹遞歸調用heap_adjust.
  
此堆調整算法的時間複雜度:
        heap_adjust(..)作用在一棵以結點index爲根的大小爲n的子樹上運行運行時間是O(1),在這個時間內完成調整元素array[index], array[lift_child(index)],array[right_child(index)]的關係,並對一index的某個子節點爲根的子樹遞歸調用heap_adjust(..). index的子節點的子樹大小約爲2n/3.最壞的情況發生在樹的最底一層恰好爲半滿的時候,這時heap_adjust的運算時間可由下式描述:
        T(n) <= T(2n/3) +O(1)
    可得:T(n) = O(lg n);

3.建堆。
    可以自底向上地用heap_adjust來將一個數組array[1..n] (n = length[array])變成一個堆,因爲,
子數組array[(n/2+1)  ....n ]中的元素都是樹上中的葉子,每個葉子都可以看成是一個元素的堆,根據堆的性質知,根是一個最值,而自底向上調整數組,就是子根與子根或是子根與葉 再和父根比較 進而可保證越往上值越大(越小)。   
    以下使建堆的僞代碼:
   

點擊(此處)摺疊或打開
build_heap( array[] ) 

  1.     
  2. for i = array_length / 2; i > 0; i--
  3.    heap_adjust(array,i );
下面是一個建堆的過程:  如圖(a),這是當i = 2 ,也就是以array[2] = 4爲根的子樹調整,因爲i是從i = 4開始調整堆的,然後i--,所以我們可以看到i = 4, i =3,時,其子樹已構成
了一個堆,所以當i =2 ,調用heap_adjust(array,i),先讓 index = 2 和2index = 4的結點比較,可得2i結點值大,然後將max_num_index=2index, 然後,用max_num_index和2index+1 = 5的結點比較,可知max_num_index沒變,到此已知待調整結點和其子結點中的最大結點下標,然後判斷max_num_index 是否等於 index(待排序結點,值爲i ),可知max_num_index != index,則將 max_num_index 結點和 index結點進行交換如圖(b); 因爲,值交換後index = 4的結點破壞了堆的性質,所以,又要堆i = 4結點進行調整,所以在heap_adjust中開始遞歸調整最後得到(c)圖,此時以i= 2爲根的子樹以滿足堆的性質;然後,在build_heap中,i--, i =1,再調用heap_adjust(array,i),然後就重複i = 2是結點調整的步驟,最後就可得一個完整的堆.



此建堆函數的時間複雜度:
        T(n) = O(n);

4.堆排序.
        堆排序,首先應該是一個堆,即先讓一個無序序列調整成一個堆,然後再進行排序.

排序的思想:
    1)首先要清楚堆的一個性質,那就是根爲最值. 因爲數組中最大(或最小)元素是在根array[1]上,因此通過訪問根就可以到達排序的效果. 
    2)可通過把它與葉子結點array[n]進行交換來達到最終的正確位置.因爲葉子都在array[n/2+1 ..n]上,讓第一最值根和最後一個葉子交換,然後,再進行堆調整.
    3)第2)中的交換產生兩種效果:
            其一,將第一最值保存到array的最後一個元素空間中(此時已進開始進行數組就地排序了!!)
            其二,葉子結點到根後就會破壞堆性質,那就會引起堆調整反應(但,此時的調整有點變化的就是待調整堆的長度,還是原來的數組長度嗎? 那肯定不是,如果還是數組長度的話,就會把葉子上的最值又調到了根上,那就會重複訪問根了,所以每次交換後,待排序的長度就要減一(待排序的堆大小),使得堆調整到達交換的葉子結點.),進而將第二最值調到了根array[1]的位置.
        然後就是將次最值和倒數第二個葉子交換(將此最值保存到第一最值的前一位置),就這樣重複1)步驟,直到最後一個元素.

下面是排序的僞代碼:
heap_length表示數組的長度, wait_adjust_length是待調整堆的長度.

點擊(此處)摺疊或打開

  1. heap_sort( array[] )
  2.    
  3.    build_heap( array);
  4.    for i = heap_length; i > 1; i--
  5.         do exchange array[1] <</SPAN>--->array[i];
  6.              wait_adjust_length = i - 1;
  7.               heap_adjust(array, 1 ,wait_adjust_length);







此排序函數的時間複雜度:
    T(n) = O(nlgn);

由於前面內容較長,再在此處列出程序,會感到很雜亂.所以我把它上傳到github上了,如有需要,可點擊查看.
c語言的堆排序的泛型程序詳見:[email protected]:zbqyexingkong/c_array_sort.git
發佈了2 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章