常用排序算法之堆排序

原理:

利用大顶堆(或小顶堆)的原理,每次将无序区堆顶元素与堆尾元素进行交换,堆尾的元素自成有序区,然后继续对剩余的堆进行堆调整,使之成为大顶堆(或小顶堆),将堆顶元素与堆尾元素进行交换,循环直到堆只有一个元素,整个排序结束。

要点:

:这里的堆并不是堆栈的那个堆,而是一颗完全二叉树,除最底层外,每一层的元素都是满的,这使得堆可以使用数组来表示。若根节点保存在数组的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); // 交换堆顶后可能破坏大顶堆的性质,故需要重新进行堆调整,使之成为大顶堆
	}
}


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