堆排序

二叉堆有一个很特殊的节点 —堆顶,堆顶是所有节点的最大元素,或者是最小元素,这主要取决于这个二叉堆是最小堆还是最大堆。基于堆顶这个特点,来实现堆排序。


用辅助数组来实现堆排序算法

如图有10个节点元素的二叉堆:

堆顶这个节点删除,然后把删除的节点放在一个辅助数组help里(黑色的节点表示已经被删除没用的点)。

被删除的节点,是堆中值最小的节点。然后继续删除二叉堆的堆顶,然后把删除的元素还是存放在help数组里。

第二次删除的节点,是原始二叉堆中的第二小节点。继续重复删除堆顶。

继续连续6次删除堆顶,把删除的节点一次放入help数组。

二叉堆中只剩最后一个节点了,这个节点同时也是原始二叉堆中的最大节点,把这个节点继续删除了,还是放入help数组里。

二叉堆的元素被删除光了,观察一下help数组。这是一个有序的数组,实际上,通过从二叉堆的堆顶逐个取出最小值,存放在另一个辅助的数组里,当二叉堆被取光之时,就完成了一次堆排序了。


无需辅助数组

 

在上面的堆排序过程中,使用了一个辅助数组help。可事实上,不需要辅助数组也能实现。

二叉堆的实现就是采取数组的形式来存储的。

从二叉堆中删除一个元素,为了充分利用空间,可以把删除的元素直接存放在二叉堆的最后一个元素那里的。例如:

删除堆顶,把删除的元素放在最后一个元素。

节点,把删除的元素放在最后第二个位置

继续删除

以此类推….

这样,对于一个含有n个元素的二叉堆,经过n-1(不用删除n次)次删除之后,这个数组就是一个有序数组了。

所以,给定无序的数组,需要先把这个数组构建成二叉堆,然后在通过堆顶逐个删除的方式来实现堆排序。

其实,也不算是删除了,相当于是把堆顶的元素与堆尾部在交换位置,然后在通过下沉的方式,把二叉树恢复成二叉堆。


代码如下:

import java.util.Arrays;

/**
 * 堆排序
 * 堆的时间复杂度是 O (nlogn)
 * 空间复杂度是 O(1)
 * @author madonghao
 * @date 2018/10/24
 */
public class HeapSort {

    /**
     * 下沉操作,执行删除操作相当于把最后
     * 一个元素赋给根元素之后,然后对根元素执行下沉操作
     * @param arr 数组
     * @param parent 要下沉元素的下标
     * @param length 数组长度
     * @return
     */
    public static int[] downAdjust(int[] arr, int parent, int length) {
        //临时保证要下沉的元素
        int temp = arr[parent];
        //定位左孩子节点位置
        int child = 2 * parent + 1;
        //开始下沉
        while (child < length) {
            //如果右孩子节点比左孩子小,则定位到右孩子
            if(child + 1 < length && arr[child] > arr[child + 1]) {
                child++;
            }
            //如果父节点比孩子节点小或等于,则下沉结束
            if(temp <= arr[child]){
                break;
            }
            //单向赋值
            arr[parent] = arr[child];
            parent = child;
            child = 2 * parent + 1;
        }
        arr[parent] = temp;
        System.out.println("每次执行的结果:" + Arrays.toString(arr));
        return arr;
    }

    /**
     * 堆排序
     * @param arr 数组
     * @param length 数组长度
     * @return
     */
    public static int[] heapSort(int[] arr, int length) {
        //构造二叉堆
        for(int i = (length - 2) / 2; i >= 0; i--) {
            arr = downAdjust(arr, i, length);
        }
        //进行堆排序
        for(int i = length - 1; i >= 1; i--) {
            //把堆顶的元素与最后一个元素交换
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            //下沉调整
            arr = downAdjust(arr, 0 , i);
        }
        return arr;
    }

    //测试用例
    public static void main(String[] args) {
        int[] arr = new int[]{1, 3, 5, 2, 0, 10, 6};
        System.out.println("开始:" + Arrays.toString(arr));
        arr = heapSort(arr, arr.length);
        System.out.println("结束:" + Arrays.toString(arr));
    }
}

执行的结果:

开始:[1, 3, 5, 2, 0, 10, 6]
每次执行的结果:[1, 3, 5, 2, 0, 10, 6]
每次执行的结果:[1, 0, 5, 2, 3, 10, 6]
每次执行的结果:[0, 1, 5, 2, 3, 10, 6]
每次执行的结果:[1, 2, 5, 6, 3, 10, 0]
每次执行的结果:[2, 3, 5, 6, 10, 1, 0]
每次执行的结果:[3, 6, 5, 10, 2, 1, 0]
每次执行的结果:[5, 6, 10, 3, 2, 1, 0]
每次执行的结果:[6, 10, 5, 3, 2, 1, 0]
每次执行的结果:[10, 6, 5, 3, 2, 1, 0]
结束:[10, 6, 5, 3, 2, 1, 0]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值