排序之堆排序

堆排序算法介绍

堆是一种重要的数据结构,为一棵完全二叉树, 底层如果用数组存储数据的话,假设某个元素为序号为i(Java数组从0开始,i为0到n-1),如果它有左子树,那么左子树的位置是2i+1,如果有右子树,右子树的位置是2i+2,如果有父节点,父节点的位置是(i-1)/2取整。分为最大堆和最小堆,最大堆的根结点不小于任意子结点,最小堆的根结点不大于任意子结点。任意子树也满足堆的特征!所谓堆排序就是利用堆这种数据结构来对数组排序。处理的思想和冒泡排序,选择排序非常的类似,一层层封顶,只是最大元素的选取使用了最大堆。


堆排序思想

给定一个数组,可以看做是一个完全二叉树(层次遍历的结果)。
完全二叉树最后一个非叶结点在(length - 1)/2处。

  1. 建堆:从下而上(从数组最后一个元素开始遍历)向上调整堆(最大堆)。
  2. 排序:将堆顶元素(索引为0的数组元素)与数组最后一个元素互换,然后再从根结点向下调整堆,循环往复。(这样,数组从尾部开始递减排序。)

代码实现

package sort;

public class HeapSort {
    public static void heapSort(int[] data) {
        if (data == null || data.length <= 0) {
            throw new IllegalArgumentException("Invalid parameters in partition method!");
        }
        int length = data.length;
        //建立堆
        for (int index = (length - 1); index > 0; index--) {
            siftUp(index, data); //向上调整堆
        }

        int tmp;
        for (int i = 0; i < (data.length - 1); i++) {
            tmp = data[0];
            data[0] = data[length - 1 - i];
            data[length - 1 - i] = tmp;
            siftDown(0, data, length - 1 - i); //向下调整堆
        }
    }

    /**
     * 向上调整堆
     *
     * @param index 被上移元素的起始位置
     * @param items
     */
    public static void siftUp(int index, int[] items) {
        int intent = items[index]; // 获取开始调整的元素对象

        while (index > 0) { // 如果不是根元素
            int parentIndex = (index - 1) / 2; // 找父元素对象的位置
            int parent = items[parentIndex];  // 获取父元素对象
            if (intent > parent) { //上移的条件,子节点比父节点大
                items[index] =  parent; // 将父节点向下放
                index = parentIndex; // 记录父节点下放的位置
            } else { // 子节点不比父节点大,说明父子路径已经按从大到小排好顺序了,不需要调整了
                break;
            }
        }

        // index此时记录是的最后一个被下放的父节点的位置(也可能是自身),所以将最开始的调整的元素值放入index位置即可
        items[index] = intent;
    }

    /**
     * 向下调整堆
     *
     * @param index 被下移的元素的起始位置
     */
    /**
     * 向下调整堆
     *
     * @param index 被下移的元素的起始位置
     * @param items
     * @param length 数组边界
     */
    public static void siftDown(int index, int[] items, int length) {
        int intent = items[index];  // 获取开始调整的元素对象
        int leftIndex = (index << 1) + 1; // // 获取开始调整的元素对象的左子结点的元素位置

        while (leftIndex < length) { // 如果有左子结点;堆是完全二叉树逻辑结构,其中某个结点没有左子结点的话就没有右子结点
            int maxChild = items[leftIndex]; // 取左子结点的元素对象,并且假定其为两个子结点中最大的
            int maxIndex = leftIndex; // 两个子节点中最大节点元素的位置,假定开始时为左子结点的位置

            int rightIndex = leftIndex + 1;  // 获取右子结点的位置
            if (rightIndex < length) {  // 如果有右子结点
                int rightChild = items[rightIndex];  // 获取右子结点的元素对象
                if (rightChild > maxChild) {  // 找出两个子节点中的最大子结点
                    maxChild = rightChild;
                    maxIndex = rightIndex;
                }
            }

            // 如果最大子节点比父节点大,则需要向下调整
            if (maxChild > intent) {
                items[index] = maxChild; // 将子节点向上移
                index = maxIndex; // 记录上移节点的位置
                leftIndex = (index << 1) + 1; // 找到上移节点的左子节点的位置
            } else { // 最大子节点不比父节点大,说明父子路径已经按从大到小排好顺序了,不需要调整了
                break;
            }
        }

        // index此时记录是的最后一个被上移的子节点的位置(也可能是自身),所以将最开始的调整的元素值放入index位置即可
        items[index] = intent;
    }
}

性能分析:不稳定

向上调整堆和向下调整堆的时间复杂度是:O(logn),跟树的高度有关。
所以,堆排序的时间复杂度是:O(nlogn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值