package org.rev.algorithm; /** * 堆排序,时间复杂度为O(nlogn),是利用堆的性质进行的一种选择排序。 * * 大顶堆是一个完全二叉树,所有的父节点都大于或等于它的左右子节点,即a[i]>=a[2i+1]&&a[i]>=a[2i+2]。 *(小顶堆是父节点<=子节点) * * 对于完全二叉树,任意节点a[i]的父节点的索引值是(i-1) / 2 向下取整。 * * 1.对于序列a[0]-a[n-1],构建大顶堆,堆顶a[0]为最大值。 * * 2. 交换堆顶a[0]和最后一个元素a[n-1],此时,a[0]-a[n-2]是无序的,a[n-1]是有序的。 * * 3. 交换后的a[0]可能是违反大顶堆的,需要再次构建大顶堆,并交换a[0]和a[n-2]。 * 此时a[0]-a[n-3]是无序的,a[n-2]-a[n-1]是有序的。 * * 4. 如此循环n次,i次循环后,a[n-i]-a[n-1]都是有序的,直到i=n,此时a[0]-a[n-1]已是有序数列。 * */ public class HeapSort { public static void main(String[] args) { int[] data = {39, 11, 38, 97, 86, 37, 12, 4, 51, 18}; // 堆排序 HeapSort hs = new HeapSort(); hs.heapSort(data); System.out.println("排序之后:"); hs.print(data); } /** * 堆排序 */ public void heapSort(int[] data) { for (int i = 0; i < data.length; i++) { // 循环建堆 buildMaxHeap(data, data.length - 1 - i); // 建堆,排序结果是升序 // buildMinHeap(data, data.length - 1 - i); // 建堆,排序结果是降序 swap(data, 0, data.length - 1 - i); // 交换堆顶和最后一个元素 print(data); } } /** * 对数组data[0]到data[lastIndex]构建大顶堆 * * @param data 等待构建大顶堆的数组 * @param lastIndex 数组中最后一个元素的索引值 */ public void buildMaxHeap(int[] data, int lastIndex) { for (int i = (lastIndex - 1) / 2; i >= 0; i--) { // 从最后一个元素的父节点开始 int k = i; // 保存当前正在判断的节点 while (2 * k + 1 <= lastIndex) { // 若节点data[k]的左子节点存在 // tmpIndex记录较大节点的索引值,初始值为当前节点的左子节点的索引值 int tmpIndex = 2 * k + 1; // 比较左右子节点哪个大 // 满足此条件说明右子节点存在,否则此时tmpIndex应该等于 lastIndex if (tmpIndex < lastIndex) { // (data[2*k+1] <data[2*k+2]),左子节点小于右子节点 if (data[tmpIndex] < data[tmpIndex + 1]) { // tmpIndex总是记录较大节点的索引值 tmpIndex++; } } // 使最大值位于父节点 if (data[k] < data[tmpIndex]) { swap(data, k, tmpIndex); // 交换两个元素在数组中的位置 k = tmpIndex; } else { break; } } } } /** * 对数组data[0]到data[lastIndex]构建小顶堆 * * @param data 等待构建小顶堆的数组 * @param lastIndex 数组中最后一个元素的索引值 */ public void buildMinHeap(int[] data, int lastIndex) { for (int i = (lastIndex - 1) / 2; i >= 0; i--) { // 从最后一个元素的父节点开始 int k = i; // 保存当前正在判断的节点 while (2 * k + 1 <= lastIndex) { // 若当前节点的子节点data[2k+1]存在 // tmpIndex,记录较小节点的索引值,初始值为当前节点的左子节点的索引值 int tmpIndex = 2 * k + 1; // 比较左右子节点哪个小 // 满足此条件说明右子节点存在,否则此时tmpIndex应该等于 lastIndex if (tmpIndex < lastIndex) { // (data[2*k+1] >data[2*k+2]),左子节点大于右子节点 if (data[tmpIndex] > data[tmpIndex + 1]) { // tmpIndex总是记录较小节点的索引值 tmpIndex++; } } // 使小值位于父节点 if (data[k] > data[tmpIndex]) { swap(data, k, tmpIndex); // 交换两个元素在数组中的位置 k = tmpIndex; } else { break; } } } } /** * 交换两个元素在数组中的位置 * * @param data * @param i * @param j */ public void swap(int[] data, int i, int j) { if (i == j) { return; } /* * int tmp = data[i]; * data[i] = data[j]; * data[j] = tmp; * * 下面三句代码和上面注释掉的三句效果一样,时间换空间的游戏。 */ data[i] = data[i] + data[j]; data[j] = data[i] - data[j]; data[i] = data[i] - data[j]; } /* * 输出数组中的元素 */ private void print(int[] data) { for (int i = 0; i < data.length; i++) { System.out.print(data[i] + "\t"); } System.out.println(); } }
堆排序方法对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上。
堆排序在最坏的情况下,其时间复杂度也为O(nlogn)。相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需一个记录大小的供交换用的辅助存储空间。
参考 http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html
时间: 2024-11-08 03:45:50