算法入门(十一):堆排序

136 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

基础原理

image.png

首先我们尝试让整个arr变成大根堆,首先处理3,然后heapinsert5,然后heapinsert9 ....

调整完了以后arr[0] = largest = 9 heapSize增加到7

image.png

其实我们在arr[0]处保证得到了数组的最大值,接着我们将其和数组最后一位交互,并heapSize--,这样数组最后一位值就确定了并且和堆断开了联系。

后面的操作就是从交换后的arr[0] = 0处开始进行heapify让剩余数字重新生成大根堆,接着arr[0]会得到次大值,接着和堆最后一位交换(length - 2)....

image.png

简单来说先heapinsert构成初始大根堆,然后再进行heapify不断更新大根堆,在此期间不断获取大值并保存

代码实现

这里核心代码包括排序过程 - heapinsert - heapify:



public static void heapSort(int[] arr){

    if(arr == null || arr.length < 2){

        return;

    }

    // 构造初始大根堆

    for(int i = 0; i < arr.length; i++){ // O(N)

        heapInsert(arr, i); // O(logN)

    }

    int heapSize = arr.length;

    swap(arr, 0 , arr.length - 1);

    // 堆化维护大根堆并交换大值

    while(heapSize > 0){ // O(N)

        heapify(arr, 0 , heapSize); // O(logN)

        swap(arr, 0 , --heapSize); // O(1)

    }

}



// ...堆插入和堆化过程之前章节已经写了

最终时间复杂度是O(N*logN)

额外空间复杂度是O(1) -> heapInsert没有申请额外变量, heapify里面也就几个变量

也没有递归空间,没有额外栈空间

扩展 - 优化基于已有数组生成大根堆过程

之前的heapinsert其实是能够很好处理用户一个数一个数给你的诉求。如果是直接给一个数组其实有优化的算法可以更好解决。

此时情况相当于用户直接给了你一个完全二叉树但不是堆,那我们其实就可以从数组最后一个数字开始作heapify,下图中最下层8个不需要操作,接着一轮保证倒数2层4棵子树都为大顶堆,然后倒数第三层2棵子树都为大顶堆...就是基于heapify操作从底向上操作即可

image.png

这样其实时间复杂度是要更好的,他的复杂度:

image.png

image.png

其实是可以完全不用heapinsert操作来实现堆排序,并且还会快一点(构建初始堆过程):

image.png