【每天进步一点点】堆排序(Heap Sort)

304 阅读3分钟

堆排序

首先!堆排序非常非常非常重要!一定要学会手写堆排序!!!

学习堆排序之前,我们要先学会两个前置知识点:形成大根堆(堆插入)、堆化

对应的英文是:heapInsert、heapify

大根堆介绍

通俗来说,大根堆就是根节点都大于子节点的完全二叉树

像下面这个图,每个根节点都大于子节点

大根堆.png

补充一下:得搞懂 “二叉树”、“完全二叉树”、“满二叉树” 之类的概念 的区别

heapInsert

此操作就是将一个个数插入到 堆中

第一步,将新增加的数加到堆的末尾

第二步,将新增加的节点与他的父节点做比较,若子节点大于父节点,父子节点交换。依次重复此操作,直到此数字到达堆顶或不再大于它的父节点

操作结果:最大的数字被扔到最顶部

图示环节

insert1.png

insert2.png

insert3.png

代码环节

    /**
     * 一个个元素插入,形成大根堆
     * @param index 数字处在数组的位置
     *              数组 index 可以转化为二叉树,2i + 1 是左孩子,(i-1) / 2 是父节点
     */
    public static void heapInsert(int[] arr, int index) {
        //当前节点的值大于父节点的值,交换两个节点的位置
        while (arr[index] > arr[(index-1) / 2]) {
            swap(arr,index,(index-1) / 2);
            index = (index-1) / 2;
        }
    }

heapify

这个操作的思路是:

1、将堆顶的最大值,和堆的最后一位交换位置,然后堆容量减1

2、然后再重新将剩余的内容变成大根堆

3、重复此操作,以此达到排序目的。

图示环节

数组乱序时:

heapify1.png

构建大根堆:

heapify2.png

heapify3.png

交换首尾元素,然后堆大小 -1

heapify5.png

此时数组的最后一位已经是有序的了!!!

heapify4.png

然后利用此数组进行 heapify(堆化) 操作,重新变成大根堆

heapify6.png

然后重复第一个步骤,将堆顶的最大值(首位),和堆的最后一位交换位置

在这里是 4 和 1 交换位置,然后对交换后的堆,进行再一次 heapify 操作,形成新的大根堆

heapify7.png

数组的情况:

heapify8.png

如此循环,直至排序完毕

代码环节

温馨提示:

左孩子下标是: i * 2 +1

右孩子下标是:左孩子下标 + 1

父节点下标是:( i -1 ) / 2

    /**
     * 在任意位置进行堆化操作,
     * @param index 数字处在数组的位置,往下做 heapify
     * @param heapSize 堆的大小。堆减至 0 说明大根堆已经全部弹出到数组中,管着左右孩子是否越界(数组的大小并不是堆的大小)
     */
    public static void heapify(int[] arr, int index, int heapSize) {
        int left = index * 2 + 1; // 左孩子下标
        while (left < heapSize) { // 有孩子,左孩子没越界
            // 左右孩子比较大小,较大数的下标,赋值给 largest 下标
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            // 孩子和父节点比大小
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                // 若自身大于左右孩子,则中断循环,不用继续比较了
                break;
            }
            swap(arr,largest,index);
            index = largest; // 往下移动
            left = index * 2 + 1; // 刷新左孩子的下标
        }
    }

堆排序代码

    public static void heapSort(int[] arr) {
        if (null == arr || 2 > arr.length) {
            return;
        }
        // 将一个个元素插入到二叉树中,形成大根堆
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i); //O(logN)
        }
        int heapSize = arr.length;
        // 将数组末尾的元素和头部元素调换位置
        // 因为上面数组已经是大根堆了,所以头部的数字肯定是最大的,然后就往最后面交换!
        swap(arr, 0, --heapSize);
        // 一直重复这个步骤,直到 heapSize 为 1
        while (heapSize > 0) {
            heapify(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }
​
    public static void heapInsert(int[] arr, int index) {
        //当前节点的值大于父节点的值,交换两个节点的位置
        while (arr[index] > arr[(index-1) / 2]) {
            swap(arr,index,(index-1) / 2);
            index = (index-1) / 2;
        }
    }
​
    public static void heapify(int[] arr, int index, int heapSize) {
        int left = index * 2 + 1; 
        while (left < heapSize) { 
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                break;
            }
            swap(arr,largest,index);
            index = largest; 
            left = index * 2 + 1; 
        }
    }
​
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }