堆排序(Heapsort)
堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,根据大根堆的要求可知,最大的值一定在堆顶。
堆排序步骤示例:
(1)、建堆: 建堆的核心内容是调整堆,使二叉树满足堆的定义(每个节点的值都不大于其父节点的值)。调堆的过程应该从最后一个非叶子节点开始,假设有数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。那么调堆的过程如下图,数组下标从0开始,A[3] = 5开始。分别与左孩子和右孩子比较大小,如果A[3]最大,则不用调整,否则和孩子中的值最大的一个交换位置,在图1中因为A[7] > A[3] > A[8],所以A[3]与A[7]对换,从图1转到图2。
(2)、调堆:如果初始数组是非降序排序,不需要调堆,本身满足堆的定义,运行时间为O(1),为最好情况;如果初始数组是如图5,只有A[0] = 1不满足堆的定义,经过与子节点的比较调整到图6,但是图6仍然不满足堆的定义,所以要递归调整,一直到满足堆的定义或者到堆底为止。如果递归调堆到堆底才结束,那么是最坏情况,运行时间为O(h) (h为需要调整的节点的高度,堆底高度为0,堆顶高度为floor(logn) )。建堆完成之后,堆如图7是个大根堆。将A[0] = 8 与 A[heapLen-1]交换,然后heapLen减一,如图8,然后AdjustHeap(A, heapLen-1, 0),如图9。如此交换堆的第一个元素和堆的最后一个元素,然后堆的大小heapLen减一,对堆的大小为heapLen的堆进行调堆,如此循环,直到heapLen == 1时停止,最后得出结果如图12。
注:子节点在数组中的下标与父节点的在数组中的下标之间的关系
堆排序示例代码
/** * 堆排序 * @author bianjie * */ public class Heapsort { /** * 建堆 * * @param data * 数据 * @param length * 数据长度 */ public static void buildHeap(int data[], int length) { // 起始的第一个父节点 int startIndex = length / 2 - 1; for (; startIndex >= 0; startIndex--) { // 调堆 adjustHeap(data, length ,startIndex); } } /** * 调整堆的大小位置 * @param data 数据数组 * @param dataLength 数据长度 * @param parentIndex 父节点,需注意父节点与子节点的关系得到的下标 */ public static void adjustHeap(int data[], int dataLength, int parentIndex) { //因为数组的首个下标为0,而堆中对应的节点比起大1,所以计算左右子节点的时候需注意 //不能直接用2*parentIndex和(2*parentIndex+1)作为左右节点在数组中的下标 //当然你也可以一开始就调节他们之间下标一一对应关系,就可以用 // 子左节点下标 int leftChildIndex = 2 * (parentIndex + 1) - 1; // 子右节点下标 int rightChildIndex = 2 *(parentIndex + 1); // 节点中的最大值的下标,初始设置为父节点为最大值 int maxValueIndex = parentIndex; while (leftChildIndex <= dataLength || rightChildIndex <= dataLength) { // 左节点存在且大于父节点 if (leftChildIndex < dataLength && data[leftChildIndex] > data[maxValueIndex]) { maxValueIndex = leftChildIndex; } // 右节点存在且大于父节点 if (rightChildIndex < dataLength && data[rightChildIndex] > data[maxValueIndex]) { maxValueIndex = rightChildIndex; } // 存在子节点的值比父节点的值大,交换两者的值 if (maxValueIndex != parentIndex) { // 进行数据位置交换 swapData(data, parentIndex, maxValueIndex); // 打印数据 printData(data); adjustHeap(data,dataLength,parentIndex); // 打乱了事先的低端排序,需要重新进行检验是否满足堆排序 parentIndex = maxValueIndex; leftChildIndex = 2 * (parentIndex + 1) - 1; rightChildIndex = 2 * (parentIndex + 1); }else{ break; } } } /** * 排序 * @param data 数据数组 */ public static void sortHeap(int data[]) { int dataLength = data.length; // 建堆 buildHeap(data, dataLength); //辅助显示排序的结果标志 int time = 1; while (dataLength > 1) { //一趟排序完得到的堆顶的最大值进行首尾位置调换, //同时将数据长度减一,起到保存已经得到的最大值的数据数列位置 swapData(data, 0, --dataLength); printData(data); System.out.println("------ 第 "+(time++)+" 趟排序完成---"); //重新进行建堆,调堆 buildHeap(data, dataLength); } } /** * 打印数据 * * @param data */ public static void printData(int data[]) { for (int i : data) { System.out.print(i + " "); } System.out.println(); } /** * 交换数据 * * @param data 数据数组 * @param firstIndex 要交换的第一个数据下标 * @param secondIndex 要交换的第二个数据下标 */ public static void swapData(int[] data, int firstIndex, int secondIndex) { int temp = data[firstIndex]; data[firstIndex] = data[secondIndex]; data[secondIndex] = temp; } public static void main(String[] args) { int data[] = { 1, 3, 4, 5, 7, 2, 6, 8, 0 }; printData(data); sortHeap(data); } }
运行结果