排序算法(7):堆排序

基本思想

  堆排序是基于这种数据结构的一种排序方法。首先将待排序的数组(或序列)构造成完全二叉树,然后利用完全二叉树中父节点和孩子节点之间的关系,每次从当前二叉树中找出最大节点并将其移出未排序部分,达到排序的目的。首先介绍一下一些相关概念:

  • 完全二叉树:对于一棵深度为 h h h 的二叉树,如果除了最后一层外,其他每层的节点数都达到最大,且第 h h h 层的节点都连续集中在最左边,那么这就是一棵完全二叉树,如下图所示:

  • :这里完整的叫法应当是二叉堆,它是一棵堆有序的完全二叉树。堆分为最大堆和最小堆,对于最大堆来说所有的父节点均大于等于两个孩子节点的值,因此根节点应当是最大值节点;相反,对于最小堆,所有的父节点均小于等于子节点的值,因此最小堆的根节点是最小值节点。

  • 堆的顺序存储结构:堆是一棵完全二叉数,因此用数组这种顺序存储结构就可以表示:

    ①:按层序遍历的顺序在数组中存放堆的元素,下标0表示的元素是根节点,其子节点分别为下标1和下标2……依次类推;

    ②:节点 i i i 如果存在左孩子,左孩子的下标为 2 i + 1 2i+1 2i+1;如果存在右孩子,右孩子的下标为 2 i + 2 2i+2 2i+2

算法流程

这里以一个长度为 n n n 的数列arr为例

  1. 首先将待排序数组构建成一个堆,此时根节点(arr[0])应当为最大值,整个数组都处于无序区
  2. 将堆中最大的元素移出。具体做法就是交换数组第一个元素 arr[0] 和 最后一个元素arr[n-1],交换之后 arr[n-1] 位于有序区,不再参与后面的排序,此时无序区由 {arr[0]~arr[n-2]} 组成;
  3. 第二步之后,无序区的元素排列是违反堆的规则的,因此要重新对无序区进行调整得到一个包含 {arr[0]~arr[n-2]} 的新堆,然后交换 arr[0] 和 最后一个元素arr[n-2], 并且将arr[n-2] 移到有序区,接下来对剩余元素重复相同的操作直至排序完成。

演示

代码实现

堆排序中,关键的一个操作就是每次调整无序区的元素使其满足堆的规则,这个操作通过adjust()函数实现:

private static void adjust(int[] arr, int i, int N){
    while(2*i+1 < N){
        int j = 2 * i + 1; // 当前节点左孩子结点的索引
        if(j+1 < N && (arr[j] < arr[j+1]))   j++;   // 找到最大的孩子节点
        if(arr[i] >= arr[j])   break;   // 表明根节点大于等于所有的孩子节点,不用交换
        int temp = arr[i];  // 交换最大孩子节点和根节点
        arr[i] = arr[j];
        arr[j] = temp;
        i = j;   
    }
}

排序函数如下:

public static void heap_sort(int[] arr){
        // 数组为空或者长度为1不需要排序
        if(arr == null || arr.length < 2){
            return;
        }
        
        int N = arr.length;
        for(int i = N/2; i >= 0; i--){
            adjust(arr, i, N);  // 通过调整使得初始堆有序
        }
        
        // 交换根节点(保存着最大元素)和最后一个孩子节点,并从堆中删除
        while(N-- > 0){
            int temp = arr[N];
            arr[N] = arr[0];
            arr[0] = temp;
            adjust(arr, 0, N);  // 重新调整数组使得堆有序
        }
    }

说明:首先对于原数组进行调整使得初始堆有序,然后通过while循环里的语句,首先交换当前堆中第一个元素和最后一个元素,并将最后一个元素移出堆(通过N–实现),再重新调整堆使其有序。

分析
  • 时间复杂度

    堆排序遍历的次数就是由初始元素构成的完全二叉树的深度,其时间复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)),最好和最差情况下也都为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

  • 空间复杂度

    堆排序使用的额外空间跟数组长度无关,因此空间复杂度是 O ( 1 ) O(1) O(1)

  • 稳定性

    如果存在重复元素,堆排序在交换堆顶元素和最后一个元素的时候,可能将原本靠后的元素前移,造成相同元素相对顺序的改变,因此堆排序是不稳定的。

堆排序是目前唯一能够同时最优地利用时间和空间的排序方法。

参考资料
  1. 一文搞定十大经典排序算法
  2. 《算法(第四版)》
  3. https://www.cnblogs.com/chengxiao/p/6129630.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值