常用排序算法之堆排序

原理:

利用大頂堆(或小頂堆)的原理,每次將無序區堆頂元素與堆尾元素進行交換,堆尾的元素自成有序區,然後繼續對剩餘的堆進行堆調整,使之成爲大頂堆(或小頂堆),將堆頂元素與堆尾元素進行交換,循環直到堆只有一個元素,整個排序結束。

要點:

:這裏的堆並不是堆棧的那個堆,而是一顆完全二叉樹,除最底層外,每一層的元素都是滿的,這使得堆可以使用數組來表示。若根節點保存在數組的0位置,則節點i的父節點爲:(i-1/2,節點i的左右子節點的下標分別爲:2*i+12*i+2。如第0個節點的左右節點分別爲12

大頂堆:堆中的每個節點的值都大於等於其左右子節點的值,即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); // 交換堆頂後可能破壞大頂堆的性質,故需要重新進行堆調整,使之成爲大頂堆
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章