原理:
利用大頂堆(或小頂堆)的原理,每次將無序區堆頂元素與堆尾元素進行交換,堆尾的元素自成有序區,然後繼續對剩餘的堆進行堆調整,使之成爲大頂堆(或小頂堆),將堆頂元素與堆尾元素進行交換,循環直到堆只有一個元素,整個排序結束。
要點:
堆:這裏的堆並不是堆棧的那個堆,而是一顆完全二叉樹,除最底層外,每一層的元素都是滿的,這使得堆可以使用數組來表示。若根節點保存在數組的0位置,則節點i的父節點爲:(i-1)/2,節點i的左右子節點的下標分別爲:2*i+1和2*i+2。如第0個節點的左右節點分別爲1和2。
大頂堆:堆中的每個節點的值都大於等於其左右子節點的值,即K[i] >= K[2*i+1]且K[i] >= K[2*i+2]。數組升序排序時使用大頂堆進行排序。
小頂堆:堆中的每個節點的值都小於等於其左右子節點的值,即K[i] =< K[2*i+1]且K[i] =< K[2*i+2]。數組降序排序時使用小頂堆進行排序。
講解(以大頂堆爲例):
一、堆的調整過程:
①節點i爲非葉子節點,且節點i小於左節點或節點i小於右節點。
②如果左節點最大,即 array[i*2+1] > array[i]且array[i*2+1] > array[i*2+2],將節點i與左節點進行交換。
③如果右節點存在且最大,即 array[i*2+2] > array[i]且array[i*2+2] > array[i*2+1],將節點i與右節點進行交換。
④對交換後的子節點遞歸進行堆調整,即循環①②③④,直到節點i爲葉子節點或節點i都不小於孩子節點。
二、構造大頂堆:
依次從最後一個非葉子節點array[size/2-1]到根節點array[0]進行堆的調整,最後得到的就是大頂堆。
三、堆排序:
設數組爲array[0...n-1].
① 先將初始數組array[0..n-1]構造成一個大頂堆,此堆爲初始的無序區。
② 再將最大的節點array[0](即堆頂)和無序區的最後一個記錄array[n-1]交換,由此得到新的無序區array[0..n-2]和有序區array[n-1],且滿足array[1..n-2] ≤ array[n-1]。
③ 由於交換後新的根array[0]可能違反大頂堆性質,故應將當前無序區array[0..n-2]調整爲大頂堆,然後再次將array[0..n-2]中最大的節點array[0]和該區間的最後一個節點array[n-2]交換,由此得到新的無序區R[0..n-3]和有序區R[n-2..n-1],且仍滿足關係R[0..n-3] ≤ R[n-2..n-1],同樣要將R[1..n-3]調整爲堆。
……
直到無序區只有一個元素爲止。
實例:
現有數組array=[9,4,10,1,2,8,3,5,7,6],要求對其進行升序排序。
一、構造大頂堆(圖1-1 ~ 圖1-8):
①從最後一個非葉子節點array[10/2-1]進行堆調整,如圖1-1。
②由於array[i=4]=2小於左節點array[9]=6,故進行交換,由於左節點爲葉子節點,故不需要對該節點進行堆調整,i--,得圖1-2。
③由於array[i=3]=1小於右節點array[8]=7,故進行交換,由於右節點爲葉子節點,故不需要對該節點進行堆調整,i--,得圖1-3。
④由於array[i=2]=10大於左右節點,故不需要進行交換,i--,得圖1-4。
⑤由於array[i=1]=4小於左節點array[3]=7,故進行交換,由於左節點爲非葉子節點,調整後可能違反大頂堆性質,故需要對左節點進行堆調整,如圖1-5。
⑥由於array[3]=4小於左節點array[7]=5,故進行交換,由於左節點爲葉子節點,故不需要對該節點進行堆調整,i--,得圖1-6。
⑦由於array[i=0]=9小於右節點array[2]=10,故進行交換,由於右節點爲非葉子節點,調整後可能違反大頂堆性質,故需要對右節點進行堆調整,如圖1-7。
⑧由於array[2]=9大於左右節點,故不需要進行交換,i-- = -1 < 0,整個過程結束,最終結果如圖1-8所示。
二、堆排序:
1.將大頂堆的堆頂array[0]與堆尾array[9]進行交換,得圖2-1,由於交換後的堆頂元素可能違反大頂堆性質,故需要進行堆調整。
2.由於array[0]=2小於右節點array[2]=9,故進行交換,由於右節點爲非葉子節點,調整後可能違反大頂堆性質,故需要對右節點進行堆調整,如圖2-2所示。
3.由於array[2]=2小於左節點array[5]=8,故進行交換,由於左節點爲葉子節點,故不需要對該節點再進行堆調整,如圖2-3所示。
4.將大頂堆的堆頂array[0]與堆尾array[8]進行交換,得圖3-1,對調整後的堆頂進行堆調整。
5.由於array[0]=1小於右節點array[2]=8,故進行交換,由於右節點爲非葉子節點,對右節點再進行堆調整,如圖3-2所示。
6.由於array[2]=1小於右節點array[6]=3,故進行交換,由於右節點爲葉子節點,無需再進行堆調整,如圖3-3所示。
7.將大頂堆的堆頂array[0]與堆尾array[7]進行交換,得圖4-1,對調整後的堆頂進行堆調整。
8.由於array[0]=4小於左節點array[1]=7,故進行交換,由於左節點爲非葉子節點,對左節點再進行堆調整,如圖4-2所示。
9.由於array[1]=4小於右節點array[4]=6,故進行交換,由於右節點爲葉子節點,無需再進行調整,如圖4-3所示。
10.將大頂堆的堆頂array[0]與堆尾array[6]進行交換,得圖5-1,對調整後的堆頂進行堆調整。
11.由於array[0]=1小於左節點array[1]=6,故進行交換,由於左節點爲非葉子節點,對左節點再進行堆調整,如圖5-2所示。
12.由於array[1]=1小於左節點array[3]=5,故進行交換,由於左節點爲葉子節點,無需再進行調整,如圖5-3所示。
13.將大頂堆的堆頂array[0]與堆尾array[5]進行交換,得圖6-1,對調整後的堆頂進行堆調整。
14.由於array[0]=2小於左節點array[1]=5,故進行交換,由於左節點爲非葉子節點,對左節點再進行堆調整,如圖6-2所示。
15.由於array[1]=2小於右節點array[4]=4,故進行交換,由於右節點爲葉子節點,無需再進行堆調整,如圖6-3所示。
16.將大頂堆的堆頂array[0]與堆尾array[4]進行交換,得圖7-1,對調整後的堆頂進行堆調整。
17.由於array[0]=2小於左節點array[1]=4,故進行交換,由於左節點爲非葉子節點,對左節點再進行堆調整,如圖7-2所示。
18.由於array[1]=2大於其孩子節點,故不需要再進行交換,如圖7-3所示。
19.將大頂堆的堆頂array[0]與堆尾array[3]進行交換,得圖8-1,對調整後的堆頂進行堆調整。
20.由於array[0]=1小於右節點array[2]=3,故進行交換,由於右節點爲葉子節點,無需再進行堆調整,如圖8-2所示。
21.將大頂堆的堆頂array[0]與堆尾array[2]進行交換,得圖9-1,對調整後的堆頂進行堆調整。
22.由於array[0]=1小於左節點array[1]=2,故進行交換,由於左節點爲葉子節點,無需再進行堆調整,如圖9-2所示。
23.將大頂堆的堆頂array[0]與堆尾array[1]進行交換,得圖10-1,對調整後的堆進行堆調整。
24.由於堆中只有一個元素,故整個排序結束,排序結果如圖10-2所示。
程序:
/**
* 堆調整程序
* @param array 待排序數組
* @param size 待排序數組大小(隨着排序越來越小)
* @param iNode 當前調整的堆節點
*/
public static void adjustHeap(int array[], int size, int iNode) {
int lChild = 2 * iNode + 1; // 節點i的左孩子節點
int rChild = 2 * iNode + 2; // 節點i的右孩子節點
int max = iNode;
int temp;
while(lChild < size || rChild < size){//非葉子節點,即存在左孩子節點或右孩子節點
if (lChild < size && array[max] < array[lChild]){// 存在左孩子節點(可不判斷,因爲是非葉子節點,肯定會有左孩子節點),且左孩子節點大於父節點
max = lChild;
}
if (rChild < size && array[max] < array[rChild]){// 存在右孩子節點,且右孩子節點大於父節點和左孩子節點
max = rChild;
}
if (iNode != max){//如果最大節點不是父節點,需要將父節點與最大節點進行交換
temp = array[max];
array[max] = array[iNode];
array[iNode] = temp;
// 交換之後可能造成被交換的孩子節點不滿足堆的性質,因此每次交換之後要重新對被交換的孩子節點進行調整
iNode = max; // 被交換的孩子節點成爲新的父節點,以備迭代調堆
lChild = 2 * iNode + 1; // 新的左孩子節點
rChild = 2 * iNode + 2; // 新的右孩子節點
} else // 如果最大節點還是父節點,則不需要再進行堆調整,繼續對下一個非葉子節點進行調堆
{
break;
}
}
}
/**
* 構造初始大頂堆
* @param array
*/
public static void buildHeap(int array[]) {
int size = array.length ;
int iNode;
int begin = array.length / 2 - 1; // 最後一個非葉子節點
for (iNode = begin; iNode >= 0; iNode--) // 從最後一個非葉子節點開始,依次對非葉子節點進行堆調整,直到堆頂
{
adjustHeap(array, size, iNode);
}
}
/**
* 堆排序
* @param array 待排序數組
*/
public static void heapSort(int array[]) {
int size = array.length ;
int temp;
//初始化大頂堆
buildHeap(array);
//堆排序
while (size > 1) { //當堆只剩下一個元素時結束
// 交換堆的第一個元素和堆的最後一個元素
temp = array[size - 1];
array[size - 1] = array[0];
array[0] = temp;
size--; // 堆的大小減1
adjustHeap(array, size, 0); // 交換堆頂後可能破壞大頂堆的性質,故需要重新進行堆調整,使之成爲大頂堆
}
}