如何基于堆实现排序?

扫码关注公众号,获取更多内容

目录

一、建堆

二、排序


堆排序的时间复杂度非常稳定,是O(nlogn),并且还是原地排序算法,我们可以把堆排序的过程大致分解为两个步骤:建堆排序

一、建堆

首先将数组原地(不借助另外一个数组,就在原数组上操作)建成一个堆,建堆有两种思路:

第一种:借助在堆中插入一个元素的思路,数组中有n个数据,我们可以假设起初堆中只有一个数据(下标为1的数据)。然后我们调用上一节讲的插入操作,将下标为2到n的数据依次插入到堆中。这样就将包含n个数据的数组,组织成了堆。

第二种:第一种的建堆的处理过程是从前往后处理数据,并且每个数据插入堆中时,都是从下往上堆化。第二种实现思路是从后往前处理数组,并且每个数据都是从上往下堆化。

如下图所示,因为叶子节点往下堆化只能自己跟自己比较,所以我们直接从最后一个非叶子节点开始,依次堆化就行了。

代码实现如下:

private static void buildHeap(int[] a, int n) {
  for (int i = n/2; i >= 1; --i) {
    heapify(a, n, i);
  }
}

private static void heapify(int[] a, int n, int i) {
  while (true) {
    int maxPos = i;
    if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;
    if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;
    if (maxPos == i) break;
    swap(a, i, maxPos);
    i = maxPos;
  }
}

上方代码中,对下标从n/2到1的数据进行堆化,从上方图中我们可以观察到,n/2+1 到 n的节点是叶子结点,我们不需要堆化。实际上,对于完全二叉树来说,下标从n/2+1到n的节点都是叶子结点。

 

二、排序

建堆完成后,数组中的数据已经符合大顶堆的特征,数组中的第一个元素就是堆顶,也就是最大的元素,我们把它跟最后一个元素交换,那最大元素就放到了下标为n的位置。这个过程有点类似“删除堆顶元素”的操作,当堆顶元素移除以后,我们把下标为n的元素放到堆顶,然后再通过堆化的方法,将剩下的n-1个元素重新构建成堆。堆化完成之后,我们再取堆顶的元素,放到下标是n-1的位置,一直重复这个过程,直到最后堆中只剩下标为1的一个元素,排序工作就完成了。

代码如下所示:

// n表示数据的个数,数组a中的数据从下标1到n的位置。
public static void sort(int[] a, int n) {
  buildHeap(a, n);
  int k = n;
  while (k > 1) {
    swap(a, 1, k);
    --k;
    heapify(a, k, 1);
  }
}

整个堆排序的过程,都只需要极个别临时存储空间,所以堆排序是原地排序算法。堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是O(n),排序过程的时间复杂度是O(nlogn),所以堆排序的整体时间复杂度是O(nlogn)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值