您的位置:首页 > 其它

堆排序

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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: