原理:
利用大顶堆(或小顶堆)的原理,每次将无序区堆顶元素与堆尾元素进行交换,堆尾的元素自成有序区,然后继续对剩余的堆进行堆调整,使之成为大顶堆(或小顶堆),将堆顶元素与堆尾元素进行交换,循环直到堆只有一个元素,整个排序结束。
要点:
堆:这里的堆并不是堆栈的那个堆,而是一颗完全二叉树,除最底层外,每一层的元素都是满的,这使得堆可以使用数组来表示。若根节点保存在数组的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); // 交换堆顶后可能破坏大顶堆的性质,故需要重新进行堆调整,使之成为大顶堆
}
}