数据结构之堆

目录

前言:

堆的特性

 如何实现一个堆?

1、往堆中插入一个元素

如何基于堆实现排序?

问题

参考资料


前言:

堆是一种特殊的树,堆这种数据结构应用场景非常多,经典的用法就是堆排序,堆排序是一种原地的、事件复杂度为O(nlogn)的排序算法。


堆的特性

什么样的树才叫做堆,堆需要满足两个特性。

  • 堆是一个完全二叉树;
  • 堆中每一个节点的值都必须大于等于(或者小于等于)其子树中每个节点的值。

完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。

对于堆汇总每个节点的值都大于等于子树中每个节点的的堆,我们叫做大顶堆,对于每个节点的值都小于等于子树中每个节点之的堆,叫小顶堆。

 其中第 1 个和第 2 个是大顶堆,第 3 个是小顶堆,第 4 个不是堆。

 如何实现一个堆?

 

 从图中我们可以看到,数组中下标为 i 的节点的左子节点,就是下标为 i∗2 的节点,右子节点就是下标为 i∗2+1 的节点,父节点就是下标为 2i​ 的节点。

1、往堆中插入一个元素

如果我们把新插入的元素放在堆的最后,那么就破坏了堆的特性,我们就需要调整让其重新满足堆的特性,这个就叫做堆化。

堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比然后交换。 

 我们可以让新插入的节点与父节点对比大小,如果不满足子节点等于父节点的大小关系,就互换两个节点。一直重复这个过程,直到父子节点之间满足关系。

代码:


public class Heap {
  private int[] a; // 数组,从下标1开始存储数据
  private int n;  // 堆可以存储的最大数据个数
  private int count; // 堆中已经存储的数据个数

  public Heap(int capacity) {
    a = new int[capacity + 1];
    n = capacity;
    count = 0;
  }

  public void insert(int data) {
    if (count >= n) return; // 堆满了
    ++count;
    a[count] = data;
    int i = count;
    while (i/2 > 0 && a[i] > a[i/2]) { // 自下往上堆化
      swap(a, i, i/2); // swap()函数作用:交换下标为i和i/2的两个元素
      i = i/2;
    }
  }
 }

如何基于堆实现排序?

我们可以把堆排序的过程大致分解成两个大的步骤,建堆和排序。

我们首先将数组原地建成一个堆。所谓“原地”就是,不借助另一个数组,就在原数组上操作。建堆的过程,有两种思路。

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

第二种实现思路,跟第一种截然相反,也是我这里要详细讲的。第一种建堆思路的处理过程是从前往后处理数组数据,并且每个数据插入堆中时,都是从下往上堆化。而第二种实现思路,是从后往前处理数组,并且每个数据都是从上往下堆化。

排序

建堆结束之后,数组中的数据已经是按照大顶堆的特性来组织的。数组中的第一个元素就是堆顶,也就是最大的元素。我们把它跟最后一个元素交换,那最大元素就放到了下标为 n 的位置。

这个过程有点类似上面讲的“删除堆顶元素”的操作,当堆顶元素移除之后,我们把下标为 n 的元素放到堆顶,然后再通过堆化的方法,将剩下的 n−1 个元素重新构建成堆。堆化完成之后,我们再取堆顶的元素,放到下标是 n−1 的位置,一直重复这个过程,直到最后堆中只剩下标为 1 的一个元素,排序工作就完成了。

问题

为什么快速排序要比堆排序性能更好?

第一点,堆排序数据访问的方式没有快速排序友好。

对于快速排序来说,数据是顺序访问的。而对于堆排序来说,数据是跳着访问的。 比如,堆排序中,最重要的一个操作就是数据的堆化。比如下面这个例子,对堆顶节点进行堆化,会依次访问数组下标是 1,2,4,8 的元素,而不是像快速排序那样,局部顺序访问,所以,这样对 CPU 缓存是不友好的。

第二点,对于同样的数据,在排序过程中,堆排序算法的数据交换次数要多于快速排序。

我们在讲排序的时候,提过两个概念,有序度和逆序度。对于基于比较的排序算法来说,整个排序过程就是由两个基本的操作组成的,比较和交换(或移动)。快速排序数据交换的次数不会比逆序度多。但是堆排序的第一步是建堆,建堆的过程会打乱数据原有的相对先后顺序,导致原数据的有序度降低。比如,对于一组已经有序的数据来说,经过建堆之后,数据反而变得更无序了。

关于堆,你还能想到它的其他应用吗?

1、从大数据量级中,筛选出top n的数据

2、求中值

3、求中位数

参考资料

本章内容来源于对王争大佬的《数据结构与算法之美》的专栏。

28 | 堆和堆排序:为什么说堆排序没有快速排序快?-极客时间

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值