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

目录

一、什么是堆

二、如何实现一个堆

1、如何存储一个堆

2、往堆中插入一个元素

3、删除堆顶元素


一、什么是堆

堆是一种特殊的二叉树,它满足如下两个条件:

1、堆是一个完全二叉树(除了最后一层,其他层的节点个数都是满的,最后一层的子节点都靠左排列)。

2、堆中每一个节点的值都必须大于等于(或小于等于)其子树中的每个节点的值。

对于每个节点的值都大于等于子树中每个节点的值的堆,叫做“大顶堆”。

对于每个节点的值都小于等于子树中每个节点的值的堆,叫做“小顶堆”。

我们来分析一下下图,看看这几个二叉树是不是堆?是什么堆?

第1个和第2个是大顶堆,第三个是小顶堆,第4个不是堆(堆是完全二叉树,最后一层的子节点都靠左排列,4中最后一层子节点靠右了)。

 

二、如何实现一个堆

1、如何存储一个堆

完全二叉树比较适合用数组来存储,用数组存储完全二叉树是比较节省存储空间的,因为不需要存储左右子节点的指针,单纯的通过数组的下标,就可以找到一个节点的左右子节点和父节点。

数组存储堆的例子如下:

数组中下标为i的节点的左子节点的下标为 i*2 的节点,右子节点就是下标为 i*2+1 的节点,父节点就是下标为 i/2 的节点。

2、往堆中插入一个元素

向堆中插入一个元素,需要继续满足堆的两个特性。

如下图所示,我们将22放到堆的最后,将会不符合堆的特性,这时候,我们需要进行调整,让其重新满足堆的特性,这个过程叫做“堆化”。

堆话分为两种,从下往上从上往下,下面讲一下从下往上的堆化方法。

堆化的过程其实并不复杂,就是顺着节点所在的路径,向上或者向下,对比,然后交换。

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

代码如下:

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;
    }
  }
 }

3、删除堆顶元素

堆中的任何节点的值都大于等于(或者小于等于)子树节点的值,我们可以发现,堆顶元素存储的就是堆中数据的最大值或者最小值。

假设我们构造的是大顶堆,则堆顶元素就是最大的元素,我们删除堆顶元素之后,就需要把第二大的元素放到堆顶,第二大元素肯定会出现在左右子节点中,我们再迭代地删除第二大节点,以此类推,直到叶子节点被删除。

分解图如下图所示,不过这种方法有点问题,就是最后堆化出来的堆并不满足完全二叉树的特性。

实际上,我们可以稍微改变一下思路,如下图所示,我们把最后一个节点放到堆顶,然后利用同样的父子节点对比方法。对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止。这就是从上往下的堆化方法

因为这种方法移除的是数组中的最后一个元素,而在堆化的过程中,都是交换操作,不会出现数组中的“空洞”,所以这种方法堆化之后的结果,一定满足完全二叉树的特性。

代码实现如下:

public void removeMax() {
  if (count == 0) return -1; // 堆中没有数据
  a[1] = a[count];
  --count;
  heapify(a, count, 1);
}

private 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;
  }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值