极客算法训练笔记(八),十大经典排序之堆排序,被树耽误的数组

十大经典排序算法江山图

图片十大经典排序算法江山图java

堆排序在排序复杂性的研究中有着重要的地位,由于他是咱们所知的惟一可以同时最优的利用空间和时间的方法,当空间十分紧张的时候(例如嵌入式系统或者低成本的移动设备中)他很流行,由于他只用几行就能实现较好的性能。可是现代操做系统中不多使用他,由于他没法利用缓存,这一点很致命。数组元素不多和相邻的其余元素进行比较,所以缓存未命中的次数要远远高于大多数比较都在相邻元素间的算法,如快速排序,归并排序,甚至是希尔排序。面试

什么是堆?

定义

堆(英语:Heap)是计算机科学中的一种特别的彻底二叉树,知足特性"给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值"。 摘自维基百科。算法

首先堆是一个彻底二叉树(除了最后一层,其余层的节点个数都是满的,最后一层的节点都靠左排列),这点很重要,直接决定了数组下标和左右父节点的对应关系(关系往下看)。数组

分类

最小堆(min heap):母节点的值恒小于等于子节点的值;缓存

最大堆(max heap):母节点的值恒大于等于子节点的值;ide

图片性能

值得注意,这个地方对哦左右子节点的大小没有要求。url

并且,堆通常用数组来存储,而不是树。由于树须要为其左右子节点分配指针空间,而数组使用数组下标来表示左右子节点的位置,能够说堆是被树耽误了的数组,顶着树的光环成功劝退了一大批想学的人,结果实际上干着数组该干的事儿,算法史上最大的骗局,挂羊肉卖狗肉spa

父节点和左右子节点在数组中的位置关系:操作系统

parent(i) = arr((i - 1)/2)
left(i)   = 2i + 1
right(i)  = 2i + 2

做用

  1. 构建优先队列
  2. 堆排序
  3. 找最值
  4. 你猜,面试官考你这个啥心态

值得注意,搜索不是堆的目的,并非每个最小(大)堆都是一个有序数组!要将堆转换成有序数组,须要使用堆排序,此处欢迎咱们的堆排序隆重登场。

堆排序

正由于堆只是父子之间大小关系肯定,左右子节点直接大小不肯定,因此才要堆排序将全部的元素有序排序。

算法描述

以构建大顶堆为例:

  1. 将待排序序列构成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点;
  2. 将其与末尾元素进行交换,此时末尾就是最大值;
  3. 而后将剩余n-1个元素从新构形成一个大顶堆,就会获得n个元素的次大值;
  4. 反复执行前面2,3步,便能获得一个有序序列

动图演示

堆排序

图解堆排序

根据数组构造大顶堆:

  1. 初始数组为:[6,1,2,3,0,4,5,7]
  2. 按照彻底二叉树,将数字依次填入,获得图中第一幅图;
  3. 从最下面的非叶子结点开始(非叶子结点为 arr.length/2-1 = 8/2-1 =3,父节点是arr[0],arr[3]也就是3)调整,根据性质,左右子节点中小的数字往上移动;
  4. 从下至上,从右往左,递归,直到根节点

构造大顶堆图片

一次调整以后7成功上位,获得数组最大的值7,与此同时数组对应的大顶堆构造完成。

由下至上的堆有序化

图片交换调整

这里能够看到,交换调整以后最大的元素到了最下面,最终会是越下面的数越大,所以构造大顶堆获得的是降序,升序要构造小顶堆。

代码实现

import com.sun.tools.javac.util.ArrayUtils;

/**
 * @author by zengzhiqin
 * 2020-12-10
 */
public class HeapSort {

    public static int[] heapSort (int[] arr) {
        if (arr ==  null && arr.length == 0) {
            throw new RuntimeException("数组不合法");
        }

        // 构建堆(从最下面叶子结点层的上一层,即倒数第二层的第一个开始进行构建调整)
        for (int i = arr.length / 2 -1; i >= 0; i--) {
            adjustDown(arr, i, arr.length);
        }

        // 循环调整下沉
        for (int i = arr.length -1; i >= 0; i--) {
            swap(arr, 0, i);
            adjustDown(arr, 0, i);
        }

        return arr;
    }

    /**
     * 调整
     * @param arr
     * @param parent
     * @param length
     */
    public static void adjustDown (int[] arr, int parent, int length) {
        // 临时元素保存要下沉的元素
        int temp = arr[parent];
        // 左节点的位置
        int leftChild = 2 * parent + 1;
        // 开始往下调整
        while (leftChild < length) {
            // 若是右孩子节点比左孩子大,则定位到右孩子 (父节点只是比左右孩子都大,左右孩子大小不肯定)
            if(leftChild + 1 < length && arr[leftChild] < arr[leftChild + 1]) {
                // 此时leftChild其实是右孩子,但始终是左右里面最大的
                leftChild ++ ;
            }
            //  大顶堆条件被破坏了
            if (arr[leftChild] <= temp)  {
                break;
            }
            // 把子节点中大的值赋值给父节点
            arr[parent] = arr[leftChild];
            // 大的子节点为父节点,调整它的左右子节点
            parent = leftChild;
            leftChild = 2 * parent + 1;
        }
        arr[parent] = temp;
    }

    /**
     * 交换数组中两个元素
     * @param arr 数组
     * @param i 父元素
     * @param index 元素2
     */
    public static void swap (int[] arr, int i, int index) {
        int temp = arr[i];
        arr[i] = arr[index];
        arr[index] = temp;
    }

    public static void main(String[] args) {
        //int[] arr = {5, 7, 8, 3, 1, 2, 4, 6, 8};
        int[] arr = {3, 1, 2, 4};
        //int[] arr = {1, 2, 3};
        arr = heapSort(arr);
        for (int i=0; i<arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

稳定性分析

不稳定。

时间复杂度分析

nlog(n)。

大顶堆构造使用 O(N) 的时间,元素调整下滤须要 O(logN),须要下滤 N-1 次,因此总共须要 O(N+(N-1)logN) = O(NlogN)。从过程能够看出,堆排序,无论最好,最坏时间复杂度都稳定在O(NlogN)。

空间复杂度分析

原地排序,空间复杂度O(1)

相关文章
相关标签/搜索