堆排序是利用堆的性質進行的一種選擇排序。下面先討論一下堆。
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位於堆頂不滿堆的性質,則需調整繼續調整
//array是待調整的堆數組,i是待調整的數組元素的位置,nlength是數組的長度
//本函數功能是:根據數組array構建大根堆
voidHeapAdjust(intarray[],inti,intnLength)
{
intnChild;
intnTemp;
for
(;2*i+1<nLength;i=nChild)
{
//子結點的位置=2*(父結點位置)+1
nChild=2*i+1;
//得到子結點中較大的結點
if
(nChild<nLength-1&&array[nChild+1]>array[nChild])
++nChild;
//如果較大的子結點大於父結點那麼把較大的子結點往上移動,替換它的父結點
if
(array[i]<array[nChild])
{
nTemp=array[i];
array[i]=array[nChild];
array[nChild]=nTemp;
}
else
//否則退出循環
break
;
}
}
//堆排序算法
voidHeapSort(intarray[],intlength)
{
inttmp;
//調整序列的前半部分元素,調整完之後第一個元素是序列的最大的元素
//length/2-1是最後一個非葉節點,此處"/"爲整除
for
(inti=length/2-1;i>=0;--i)
HeapAdjust(array,i,length);
//從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
for
(inti=length-1;i>0;--i)
{
//把第一個元素和當前的最後一個元素交換,
//保證當前的最後一個位置的元素都是在現在的這個序列之中最大的
///Swap(&array[0],&array[i]);
tmp=array[i];
array[i]=array[0];
array[0]=tmp;
//不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值
HeapAdjust(array,0,i);
}
}