堆排序

堆排序

堆排序的主要思想

关于什么是堆,可以看看二叉堆。这里就不在啰嗦了。

关于堆,我们知道有个最主要的性质,对于最大堆,堆顶的元素总是最大的,对于最小堆,堆顶的元素总是最小的。所以如果我们将一个数组转化成一个堆,在反复执行删除堆顶元素的操作,那么每次删除的元素必然是有序的,也就实现了我们排序的效果。

因此,堆排序的步骤主要两点:

  1. 构建堆,将一个数组转化成一个堆,我们在MaxHeap.md中有总结,时间复杂度是O(n log2n)
  2. 遍历删除堆顶的元素,我们知道删除堆顶元的时间复杂对是O(log2n),那么如果堆中有n个元素,则时间复杂度变成O(nlog2n)。由此得出堆排序的时间复杂度是O(nlog2n)。

综上所述,我们发现如果这样做,那么时间复杂度是没有问题的,但是空间上则需要使用一个附加的数组来存储每次删除的元素,使得存储需求增加一倍。那么这个问题怎么规避呢,我们知道删除堆顶元素后,为了维护堆的性质,我们的做法是将最后一个元素放到堆顶位置,在进行siftDown()操作。比如下图,我们删除97后,会将31放到堆顶(这个时候31的位置就空下来了),然后调整堆。那么我们就可以利用这一点,将97放到空下来的位置。然后依次类推,我们在删除59,那么还是31的位置空下来,我们在将59放到空下来的位置,直到堆中剩下一个元素,这个时候你会发现数组已经是有序的了,并且没有额外占用空间。
在这里插入图片描述
代码实现如下:


    // 获取节点的左孩子索引
    private static int getLeft(int index) {
        return 2 * index + 1;
    }

    // 获取节点的右孩子索引
    private static int getRight(int index) {
        return 2 * index + 2;
    }

    private static void swapReference(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

	// 下沉操作调整堆 O(logN)
    private static void siftDown(int[] arr, int index, int size) {
        int child = 0;
        int tmp = arr[index];
        for (; getLeft(index) < size; index = child) {
            // 找到左右孩子中较大的元素
            child = getLeft(index);
            if (child != size - 1 &&
                    arr[getLeft(index)] < arr[getRight(index)]) {
                child = getRight(index);
            }
            if (tmp < arr[child]) {
                arr[index] = arr[child];
            } else {
                break;
            }
        }
        arr[index] = tmp;
    }

    // 构建堆
    private static void buildHeap(int[] arr) {
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            siftDown(arr, i, arr.length);
        }
    }

    /**
     * 堆排序
     *
     * @param arr 待排序数组
     */
    public static void heapSort(int[] arr) {
        buildHeap(arr);
        // 遍历执行deleteMax方法,因为删除一个堆顶元素,堆尾元素就空出来了,
        // 将删除的元素放到堆尾,这样就达到了排序效果,并且利用了堆的原始数组,节省了空间
        for (int i = arr.length - 1; i > 0; i--) {
            swapReference(arr, 0, i);
            siftDown(arr,0, i);
        }
    }

git地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半__夏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值