堆排序
2017-08-12 23:24
204 查看
理论简介
建立初始堆首末元素互换, 即得到最大元素放入数组最末尾.
调整堆. 第二步的操作明显会将堆破坏, 所以需要调整堆.
跳回第二步.
建立初始堆
在建堆之前需要将数组转成二叉树图, 方便理解:如果将
父>左子|右子当做树的最小单元组, 称为
父子单元, 那么只需要保证每个
父子单元满足最大堆规则, 那么整体树就满足了最大堆.
==>定义一个方法(
unitAdjust())用来调整
父子单元, 将单元中最大的值推到该单元的根部, 成为父, 原来的父降到最大值之前的位置, 作为子. 但是这样有可能使得以这个新子为父结点的下一层的
父子单元不满足最大堆规则. ==>设置规则, 如果发生了交换, 则递归调用方法自身, 调整
新子为父结点的
父子单元. 如此反复.
上述过程可以说是自上而下的下钻过程.
但是如果使得整个树成为最大堆, 那么就是需要针对树中的每个
父子单元进行调整. 只需要对树中的每个非叶子节点进行遍历, 使用上述方法
unitaAdjust()调整即可. 进一分析, 可以发现这里有个问题: 遍历的方向是自上而下还是自下而上呢?
如果自上而下会出现什么情况呢?
如下图, 第一趟循环中, 操作的对象是以根结点
34为父结点的
父子单元. 循环体内部将调用
unitAdjust(), 34和50将互换, 由于发生了交换,
unitAdust()方法将发生递归调用, 去操作以
新子34为父结点的
父子单元, 经过比较, 未发生交换情况, 第一趟循环结束. 图中发生了比较或者互换的位置以绿色标出, 这时聪明的你不难发现, 当程序向下遍历走到第三个节点时, 便发生了重复比较, 当然你或许能够想到规避重复比较的方法, 但是我是没有想出, 或者也可以认为重复比较无所谓.
其实, 换一种思路, 进行自下而上遍历情况就完全不一样了.
1. 从最后一个非叶子节点67开始, 67↔90
2. 50节点不动
3. 23↔90, 触发下钻机制.
1. 23↔67
4. 34↔90, 触发下钻机制
1. 34↔67, 再次下钻
2. 未动
最终得到初始堆如下图所示, 其中蓝色表示交换过位置.
可见, 整体是自下而上遍历, 具体单元操作时采取自上而下, 总之, 自上而下和自下而上的相结合.
建立堆的过程其实有点
冒泡的味道, 数值大逐渐的冒到上面, 好像密度大的即使开始在水底部, 但随着遍历, 密度大的终将
上浮到上部.
堆排序
初始堆建立好了后, 堆顶的元素自然就是最大值, 于是将第一个元素和最后一个元素互换, 即完成将最大值放到最末尾, 完了第一次排序.由于交换使得堆被破坏, 所以需要对的n-1个元素进行调整, 使其成为堆. 具体做法就是调用
unitAdjust()对以根节点为父结点的
父子单元进行调整, 刚换来的最末位元素自然会被移到子节点位置, 这样递归调用机制被触发, 于是继续保证所有下层调整为堆.
接着将倒数第二个元素和堆顶元素互换, ……如此反复.
代码实现
思路://建立初始堆// for (i=n/2;i>=1;i--) { unitAdjust() } //堆排序// for (i=n; i>=1; i--) { 首末交换 unitAdjust()调整 }
Java代码:
测试结果:
- 10亿长度, 内存爆掉;
- 1亿长度, 35693ms.
package LearningLog; /* * - 10亿长度, 内存爆掉 * - 1亿长度, 35693ms. */ public class demo25 { public static void main(String[] args) { int[] arr=MyJava.randomArr(100000000, 10000,123); // System.out.println(Arrays.toString(arr)); // initHeap(arr); long t0 = System.currentTimeMillis(); heapSort(arr); long t1 = System.currentTimeMillis(); System.out.println((t1-t0)+"ms"); // System.out.println(Arrays.toString(arr)); } /* ====unitAdjust()=========================================== * 父子单元调整 * I: 父结点的索引, 数组长度: 非常非常非常关键, 因为后续调用的时候, 需要卡住最大的遍历位置, 否则将会将尾部已经排好序的较大数值顶到上面去了, 那就糟糕呕吐了 */ public static void unitAdjust(int array[],int fatherNode, int sub_size) { int lchild = 2*fatherNode+1; int rchild = lchild+1; int max = fatherNode; // 默认最大值索引就是父结点的索引 int tmp = 0; if (fatherNode<sub_size/2) { //只对数组的非叶子节点操作 if (lchild<sub_size && array[fatherNode]<array[lchild]) { max = lchild; } if (rchild<sub_size && array[max]<array[rchild]) { //rchild<N 首先的保证有 右子 max = rchild; } if (max != fatherNode) { //最大值不再是父结点时, 需要交换 tmp = array[fatherNode]; array[fatherNode] = array[max]; array[max] = tmp; unitAdjust(array, max, sub_size); //如果发生交换, 则触发递归调用, 保证下层的满足堆 } } } /* * ====initHeap()============================================ 4000 * 建立初始堆 */ public static void initHeap(int[] array) { // TODO Auto-generated method stub for (int i=array.length/2-1; i>=0; i--) { unitAdjust(array, i, array.length); //大的值不断上浮 } } /* * ====heapSort()================================================= * 堆排序 */ public static void heapSort(int[] array) { int tmp=0; //建立堆// initHeap(array); for(int i=array.length-1; i>=0; i--) { //首尾互换/ tmp = array[i]; array[i] = array[0]; array[0] = tmp; //剩下的i-1个元素需要调整// unitAdjust(array, 0, i); //始终只用调整第一个父子单元即可 } } }
参考资料:
http://blog.csdn.net/xiaoxiaoxuewen/article/details/7570621/