堆排序
堆排序是利用堆的性質進行的一種選擇排序。下面先討論一下堆。
1.堆
堆實際上是一棵完全二叉樹,其任何一非葉節點滿足性質:
Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]
即任何一非葉節點的關鍵字不大於或者不小於其左右孩子節點的關鍵字。
堆分為大頂堆和小頂堆,滿足Key[i]>=Key[2i+1]&&key>=key[2i+2]稱為大頂堆,滿足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]稱為小頂堆。由上述性質可知大頂堆的堆頂的關鍵字肯定是所有關鍵字中最大的,小頂堆的堆頂的關鍵字是所有關鍵字中最小的。
2.堆排序的思想
利用大頂堆(小頂堆)堆頂記錄的是最大關鍵字(最小關鍵字)這一特性,使得每次從無序中選擇最大記錄(最小記錄)變得簡單。
其基本思想為(大頂堆):
1)將初始待排序關鍵字序列(R1,R2....Rn)構建成大頂堆,此堆為初始的無須區;
2)將堆頂元素R[1]與最后一個元素R[n]交換,此時得到新的無序區(R1,R2,......Rn-1)和新的有序區(Rn),且滿足R[1,2...n-1]<=R[n];
3)由於交換后新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,......Rn-1)調整為新堆,然后再次將R[1]與無序區最后一個元素交換,得到新的無序區(R1,R2....Rn-2)和新的有序區(Rn-1,Rn)。不斷重復此過程直到有序區的元素個數為n-1,則整個排序過程完成。
操作過程如下:
1)初始化堆:將R[1..n]構造為堆;
2)將當前無序區的堆頂元素R[1]同該區間的最后一個記錄交換,然后將新的無序區調整為新的堆。
因此對於堆排序,最重要的兩個操作就是構造初始堆和調整堆,其實構造初始堆事實上也是調整堆的過程,只不過構造初始堆是對所有的非葉節點都進行調整。
下面舉例說明:
給定一個整形數組a[]={16,7,3,20,17,8},對其進行堆排序。
首先根據該數組元素構建一個完全二叉樹,得到



20和16交換后導致16不滿足堆的性質,因此需重新調整
這樣就得到了初始堆。
此時3位於堆頂不滿堆的性質,則需調整繼續調整









#include<stdio.h> typedef int ElementType; int arr1[11]={0,2,87,39,49,34,62,53,6,44,98}; #define LeftChild(i) (2 * (i) + 1) void Swap(int* a,int* b) { int temp=*a; *a=*b; *b=temp; } void PercDown(int A[], int i, int N) { int child; ElementType Tmp; for (Tmp = A[i]; 2*i+1 < N; i = child){ child = 2*i+1; //注意數組下標是從0開始的,所以左孩子的求發不是2*i if (child != N - 1 && A[child + 1] > A[child]) ++child; //找到最大的兒子節點 if (Tmp < A[child]) A[i] = A[child]; else break; } A[i] = Tmp; } void HeapSort(int A[], int N) { int i; for (i = N / 2; i >= 0; --i) PercDown(A, i, N); //構造堆 for(i=N-1;i>0;--i) { Swap(&A[0],&A[i]); //將最大元素(根)與數組末尾元素交換,從而刪除最大元素,重新構造堆 PercDown(A, 0, i); } } void Print(int A[],int N) { int i; for(i=0;i<N;i++) { printf(" %d ",A[i]); } } int main() { int arr[10]={2,87,39,49,34,62,53,6,44,98}; Print(arr,10); printf("\n"); HeapSort(arr,10); Print(arr,10); printf("\n"); return 0; }
運行結果:

