【算法与数据结构专场】堆排序是什么鬼?

排序算法相必你们都见过不少种,例如快速排序、归并排序、冒泡排序等等。今天,咱们就来简单讲讲堆排序vue

在上一篇中,咱们讲解了二叉堆,今天的堆排序算法主要就是依赖于二叉堆来完成的,不清楚二叉堆是什么鬼的,能够看下:java

【算法与数据结构】二叉堆是什么鬼?算法

 

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

 

假如给你一个二叉堆,根据二叉堆的特性,你会怎么使用二叉堆来实现堆排序呢?数组

咱们都知道,二叉堆有一个很特殊的节点 —- 堆顶,堆顶要嘛是全部节点的最大元素,要嘛是最小元素,这主要取决于这个二叉堆是最小堆仍是最大堆markdown

今天,咱们暂且选择以最小堆来做为例子。数据结构

基于堆顶这个特色,咱们就能够来实现咱们的堆排序了。app

你们看下面一个例子:测试

对于一个如图有10个节点元素的二叉堆:ui

咱们把堆顶这个节点删除,而后把删除的节点放在一个辅助数组help里。url

显然,这个被删除的节点,是堆中最小的节点。接下来,咱们继续删除二叉堆的堆顶,而后把删除的元素仍是存放在help数组里。

显然,第二次删除的节点,是原始二叉堆中的第二小节点。

继续删除

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

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

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

其实无需辅助数组

 

在上面的堆排序过程当中,咱们使用了一个辅助数组help。可事实上,咱们真的须要辅助数组吗?

上篇讲二叉堆的时候,咱们说过。二叉堆在实现的时候,是采起数组的形式来存储的。

从二叉堆中删除一个元素,为了充分利用空间,其实咱们是能够把删除的元素直接存放在二叉堆的最后一个元素那里的。例如:

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

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

继续删除,把删除的元素放在最后第三个位置

以此类推….

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

因此,给你一个无序的数组,咱们须要把这个数组构建成二叉堆,而后在经过堆顶逐个删除的方式来实现堆排序。

其实,也不算是删除了,至关因而把堆顶的元素与堆尾部在交换位置,而后在经过下沉的方式,把二叉树恢复成二叉堆。

代码以下:

public class HeapSort {
    /** * 下沉操做,执行删除操做至关于把最后 * * 一个元素赋给根元素以后,而后对根元素执行下沉操做 * @param arr * @param parent 要下沉元素的下标 * @param length 数组长度 */
    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;
        return arr;
    }

    //堆排序
    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));
    }
}

对于堆的时间复杂度,我就直接给出了,有兴趣的能够本身推理下,仍是不难的。堆的时间复杂度是 O (nlogn)。空间复杂度是 O(1)。

这里可能你们会问,堆排序的时间复杂度是O (nlogn),像快速排序,归并排序的时间复杂度也是 O(nlogn),那我在使用的时候该如何选择呢?

这里说明一下:快速排序是平均复杂度 O(logn),实际上,快速排序的最坏时间复杂度是O(n^2。),而像归并排序,堆排序,都稳定在O(nlogn)

我给出一个问题,例如给你一个拥有n个元素的无序数组,要你找出第 k 个大的数,那么你会选择哪一种排序呢?

显然在这个问题中,选用堆排序是最好的,咱们不用把数组所有排序,只须要排序到前k个数就能够了。至于代码如何实现,这个我就不给代码了,你们能够动手敲一敲。

推荐阅读:

【算法与数据结构】二叉堆是什么鬼?

获取更多原创文章,能够关注下个人公众号:苦逼的码农,我会不按期分享一些资源和软件等。后台回复礼包送你一份时下热门的资源大礼包。

相关文章
相关标签/搜索