基本思想
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
堆的定義如下:具有n個元素的序列(k1,k2,…,kn),當且僅當滿足
時稱之爲堆。由堆的定義可以看出,堆頂元素(即第一個元素)必爲最小項(小頂堆)。
若以一維數組存儲一個堆,則堆對應一棵完全二叉樹,且所有非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。如:
(a)大頂堆序列:(96, 83,27,38,11,09)
(b) 小頂堆序列:(12,36,24,85,47,30,53,91)
初始時把要排序的n個數的序列看作是一棵順序存儲的二叉樹(一維數組存儲二叉樹),調整它們的存儲序,使之成爲一個堆,將堆頂元素輸出,得到n個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。然後對前面(n-1)個元素重新調整使之成爲堆,輸出堆頂元素,得到n個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。稱這個過程爲堆排序。
因此,實現堆排序需解決兩個問題:
- 如何將n 個待排序的數建成堆;
- 輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其成爲一個新堆。
首先討論第二個問題:輸出堆頂元素後,對剩餘n-1元素重新建成堆的調整過程。
調整小頂堆的方法:
1)設有m 個元素的堆,輸出堆頂元素後,剩下m-1個元素。將堆底元素送入堆頂((最後一個元素與堆頂進行交換),堆被破壞,其原因僅是根結點不滿足堆的性質。
2)將根結點與左、右子樹中較小元素的進行交換。
3)若與左子樹交換:如果左子樹堆被破壞,即左子樹的根結點不滿足堆的性質,則重複方法 (2).
4)若與右子樹交換,如果右子樹堆被破壞,即右子樹的根結點不滿足堆的性質。則重複方法 (2).
5)繼續對不滿足堆性質的子樹進行上述交換操作,直到葉子結點,堆被建成。
稱這個自根結點到葉子結點的調整過程爲篩選。如圖:
再討論對n 個元素初始建堆的過程。
建堆方法:對初始序列建堆的過程,就是一個反覆進行篩選的過程。
1)n 個結點的完全二叉樹,則最後一個結點是第個結點的子樹。
2)篩選從第個結點爲根的子樹開始,該子樹成爲堆。
3)之後向前依次對各結點爲根的子樹進行篩選,使之成爲堆,直到根結點。
如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)
算法的實現
從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。
#include<stdio.h>
/**
* 已知H[s…m]除了H[s] 外均滿足堆的定義
* 調整H[s],使其成爲大頂堆.即將對第s個結點爲根的子樹篩選,
*
* @param H是待調整的堆數組
* @param s是待調整的數組元素的位置
* @param length是數組的長度
*
*/
void heapAdjust(int H[],int s,int length){
int temp = H[s];
int child = 2*s+1;//左孩子結點的位置。(i+1 爲當前調整結點的右孩子結點的位置)
while(child<length){
if(child+1<length && H[child]<H[child+1]){// 如果右孩子大於左孩子(找到比當前待調整結點大的孩子結點)
++child;
}
if(H[s]<H[child]){// 如果較大的子結點大於父結點
H[s] = H[child];// 那麼把較大的子結點往上移動,替換它的父結點
s = child;// 重新設置s ,即待調整的下一個結點的位置
child = 2*s+1; // 如果當前待調整結點大於它的左右孩子,則不需要調整,直接退出
}else{
break;
}
H[s] = temp; // 當前待調整的結點放到比其大的孩子結點位置上
}
}
/**
* 初始堆進行調整
* 將H[0..length-1]建成堆
* 調整完之後第一個元素是序列的最小的元素
*/
void buildHeap(int H[],int length){
int i;
for(i=(length-1)/2;i>=0;i--){//最後一個有孩子的節點的位置 i= (length -1) / 2
heapAdjust(H,i,length);
}
}
/**
* 堆排序算法
*/
void heapSort(int H[],int length){
int i,temp;
buildHeap(H,length);//初始化堆
for(i=length-1;i>=0;i--){//從最後一個元素開始對序列進行調整
temp = H[i]; //交換堆頂元素H[0]和堆中最後一個元素
H[i] = H[0];
H[0] = temp;
heapAdjust(H,0,i);//每次交換堆頂元素和堆中最後一個元素之後,都要對堆進行調整
}
}
/**
* 打印
*/
void print(int r[],int n){
int i;
for(i=0;i<n;i++){
printf("%3d",r[i]);
}
printf("\n");
}
int main(){
int H[10] = {3,1,5,7,2,4,9,6,10,8};
printf("排序前:\n");
print(H,10);
printf("堆排序後:\n");
heapSort(H,10);
print(H,10);
return 0;
}