【筆記】堆排序

Time-space發表於2017-11-11

  堆排序只需要記錄一個記錄大小的輔助空間,每個待排序的記錄僅佔有一個儲存空間。
  **堆的定義:**n個元素的序列{k1,k2,,kn}

\{k_1,k_2,…,k_n\}
當且僅當滿足如下關係時,稱之為堆。

kik2ikik2i+1
\begin{cases} k_i \leq k_{2i}\\ &&&&\\ k_i \leq k_{2i+1} \end{cases}


kik2ikik2i+1
\begin{cases} k_i \geq k_{2i}\\ &&&&\\ k_i \geq k_{2i+1} \end{cases}

(i = 1,2,3,…,[n2]

[\frac {n}{2}]
)

  若將和此序列對應的一維陣列看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端節點的值均不大於其左、右孩子結點的值。由此,若序列{k1,k2,,kn}

\{k_1,k_2,…,k_n\}
是堆,則堆頂元素必定為序列中n個元素的最小值(或最大值)。

  例如序列{96,83,27,38,11,09}和{12,36,24,85,47,30,53,91}對應的完全二叉樹為:


這裡寫圖片描述

  若在輸出堆頂的最小值之後,使得剩餘n-1個元素的序列重又建成一個堆,則得到n個元素中的次小值。如此反覆執行,便能得到一個有序序列,這個過程稱之為堆排序

  • 調整堆

  如下圖所示,假設輸出堆頂元素之後,以堆中最後一個元素替代,此時根結點的左、右子樹均為堆,則僅需自上至下調整即可。首先以堆頂元素和其左、右子樹根結點的值進行比較,由於右子樹根結點的值相愛哦與左子樹結點的值,則將27和97交換,由於97的替代了27之後破壞了右子樹的堆,則需進行和上述相同的調整,直至葉子結點,此時堆頂為n-1個元素中的最小值。重複上述過程,將堆頂元素27和堆中最後一個元素97交換且調整,得到一個新的堆。我們稱這個自堆頂至葉子的調整過程為篩選


這裡寫圖片描述

  • 建堆

  從一個無序序列建堆的過程就是一個反覆篩選的過程。若將次序列看成是一個完全二叉樹,則最後一個非終端結點是第[n2]

[\frac{n}{2}]
個元素,由此篩選只需從第[n2]
[\frac{n}{2}]
個元素開始。

  例如下圖中的二叉樹表示一個有8個元素的無序序列{49,38,65,97,76,13,27,49},則篩選從第4個元素開始,由於97>49,則交換之。同理,在第3個元素65被篩選之後序列的狀態如圖所示。由於第2個元素38不大於其左、右子樹根的值,則篩選後的序列不變。


這裡寫圖片描述

  基本演算法思想:使記錄序列按關鍵字非遞減有序排列,則在堆排序的演算法中先建立一個最大堆,即先選得一個關鍵字為最大的記錄並與序列中最後一個記錄交換,然後對序列中前n-1記錄進行篩選,重新將它調整為一個最大堆,如此分那副直至排序結束。

  • 型別定義
 #include<stdio.h>
 #define EQ(a,b) ((a)==(b))
 #define LT(a,b) ((a)<(b))
 #define LQ(a,b) ((a)<=(b))
 #define N 8
 #define MAXSIZE 20 /* 一個用作示例的小順序表的最大長度 */
 typedef int InfoType; /* 定義其它資料項的型別 */
 typedef int KeyType; /* 定義關鍵字型別為整型 */
 typedef struct
 {
   KeyType key; /* 關鍵字項 */
   InfoType otherinfo; /* 其它資料項,具體型別在主程中定義 */
 }RedType; /* 記錄型別 */

 typedef struct
 {
   RedType r[MAXSIZE+1]; /* r[0]閒置或用作哨兵單元 */
   int length; /* 順序表長度 */
 }SqList; /* 順序表型別 */
 typedef SqList HeapType; /* 堆採用順序表儲存表示 */
  • 堆排序函式
void HeapAdjust(HeapType *H,int s,int m) /* 演算法10.10 */
 { /* 已知H.r[s..m]中記錄的關鍵字除H.r[s].key之外均滿足堆的定義,本函式 */
   /* 調整H.r[s]的關鍵字,使H.r[s..m]成為一個大頂堆(對其中記錄的關鍵字而言) */
   RedType rc;
   int j;
   rc=(*H).r[s];
   for(j=2*s;j<=m;j*=2)
   { /* 沿key較大的孩子結點向下篩選 */
     if(j<m&&LT((*H).r[j].key,(*H).r[j+1].key))
       ++j; /* j為key較大的記錄的下標 */
     if(!LT(rc.key,(*H).r[j].key))
       break; /* rc應插入在位置s上 */
     (*H).r[s]=(*H).r[j];
     s=j;
   }
   (*H).r[s]=rc; /* 插入 */
 }

 void HeapSort(HeapType *H)
 { /* 對順序表H進行堆排序。演算法10.11 */
   RedType t;
   int i;
   for(i=(*H).length/2;i>0;--i) /* 把H.r[1..H.length]建成大頂堆 */
     HeapAdjust(H,i,(*H).length);
   for(i=(*H).length;i>1;--i)
   { /* 將堆頂記錄和當前未經排序子序列H.r[1..i]中最後一個記錄相互交換 */
     t=(*H).r[1];
     (*H).r[1]=(*H).r[i];
     (*H).r[i]=t;
     HeapAdjust(H,1,i-1); /* 將H.r[1..i-1]重新調整為大頂堆 */
   }
 }
  • 主程式
void print(HeapType H)
 {
   int i;
   for(i=1;i<=H.length;i++)
     printf("(%d,%d)",H.r[i].key,H.r[i].otherinfo);
   printf("\n");
 }


 void main()
 {
   RedType d[N]={{49,1},{38,2},{65,3},{97,4},{76,5},{13,6},{27,7},{49,8}};
   HeapType h;
   int i;
   for(i=0;i<N;i++)
     h.r[i+1]=d[i];
   h.length=N;
   printf("排序前:\n");
   print(h);
   HeapSort(&h);
   printf("排序後:\n");
   print(h);
 }
  • 測試結果


這裡寫圖片描述

相關文章