数据结构和算法之十:堆树

本文介绍了堆树的概念,特别是大顶堆,作为优先队列的基础。堆树是一棵完全二叉树,其中每个父节点的值大于其子节点。详细解释了如何通过插入新节点并进行向上堆化来维护堆树结构,以及删除节点时执行的向下堆化过程。堆排序的逻辑也被探讨,核心是不断进行堆化操作。文章提供了可视化资源帮助理解堆树和堆排序的工作原理。
摘要由CSDN通过智能技术生成

数据结构树论之堆树

堆树,作为二叉树中的一个重要成员,常用于优先队列、TOPK等问题中。

在上一文中,我们使用优先队列非常方便的构建出了赫夫曼树,那么你知道优先队列是怎么实现的呢?

堆树长啥样子,我们先画个图认识一下:(这是大顶堆)

在这里插入图片描述

首先,堆树是一颗完全二叉树(完全二叉树的定义你应该还知道吧),同时满足每个父亲节点的值都大于其孩子节点。(每个父亲节点的值都大于其孩子节点的话,就叫小顶堆,为了简化,本文都用大顶堆来举例)

既然堆树是一个完全二叉树,那么我们就可以使用数组来存储,左孩子的下标是父亲节点的2倍,右孩子是2倍+1(根节点的下标从1算起)。

对于一课已经存在的堆树,如何插入一个新的节点?步骤如下

  1. 新节点插入到最末尾
  2. 新节点从下往上堆化

插入到最末尾容易理解,从下往上堆化是个什么逻辑?这个画个图来理解就非常容易了

在这里插入图片描述
在这里插入图片描述

比如,上图堆树中新插入6,第一步,将6插入到末尾;然后新节点6和其父亲节点比较,如果大于父亲节点,则和父亲节点交换;然后继续和上一层的父亲节点比较,直到不再交换或者比较到根为止。这个不断和父亲比较和交换的动作,就叫向上堆化。

理解了这个插入的过程,删除元素的核心流程一样,删除分两种情况:

  1. 如果删除的是最后一个节点,那么直接删除
  2. 删除非最后一个节点时,首先将删除节点和最后一个节点交换,然后执行一次向下堆化;删除最后一个节点即可。

同样,删除画个图理解一下

在这里插入图片描述

要删除节点10,首先,将节点10和最后一个节点6交换:

在这里插入图片描述

然后对6从上到下进行一次堆化,即和两个孩子比较,如果最大的那个孩子比自己还大,则和最大的孩子交换,示例中的就是6和8交换:

在这里插入图片描述

继续往下比较和交换,直到不发生交换,或者交换到叶子节点为止

在这里插入图片描述

最后,将节点10删除即可。

通过上面的分析,插入和删除的核心就是堆化,比较和交换的次数最多为logn,因此插入和删除的时间复杂度为logn。

明白了插入和删除,那么构建呢,如果从0开始一个元素一个元素的插入构建,相信大家已经会了;但是如果给你一个数组(也就是一个完全二叉树),如何就在本数组完成堆化呢?

这个过程可以从后往前进行向下堆化即可,从倒数第一个非叶子节点开始即可,因为叶子节点的向下堆化(和孩子比较)是没有任何效果的。同样,图示一波

在这里插入图片描述

倒数第一个非叶子节点是5, 它向下堆化后,保持原位。第二次,往前一个位置,对7进行向下堆化,它会和10进行交换;再往前对4进行向下堆化,它会和10交换位置。

在这里插入图片描述
因为发生了交换,同时还没有到叶子节点,因此继续往下堆化,与8发生交换。

在这里插入图片描述

到此就完成了数组的堆化过程,是不是很巧妙。

可以看出,堆树上的核心操作都和堆化离不开,一定要理解这个堆化的过程。

现在我们可以使用堆树来实现自己的优先队列了,添加元素对应插入,弹出元素就是删除根节点。

在排序算法的两篇文章中,还有一种排序算法没说,那就是堆排序,下面我们来看看堆排序是个什么逻辑。

你应该想到,核心依然是堆化,那么整个流程是怎样的呢?

  1. 将根和最后一个节点交换,也就是最大的元素跑到最后去了
  2. 然后从根开始往下进行一次堆化(注意堆化的过程排除已经排好序的),完成之后根就变为第二大的元素了
  3. 重复1、2步骤,直到剩下最后一个元素为止。

同样,我们画图来理解:

在这里插入图片描述

第一步,最大的元素就放到最后了。

在这里插入图片描述
第二步,堆化之后第二大的元素就到根上了

重复这个过程,唯一注意的是在堆化时,要排除已经交换到最后去的,已经排好序的元素。

堆树也就这么回事,画画图也就没那么难理解了,最后看下堆树的代码,其中没有实现删除的操作,你能把删除的代码补上吗?

/**
 * 大顶堆树
 *
 *  性质:1。是一颗完全二叉树; 2. 根节点的值大于子节点的值。
 *
 *  因为是一颗完全二叉树,因此可以使用数组存储,数组下标为i的节点,(根节点下标为1)其左孩子下标为2*,右孩子下标为2i+1
 *
 *  堆化过程
 *  情况1, 如果数据是动态的,一个一个插入,可通过两种方式插入处理。
 *  方式1, 从下往上堆化,即新插入的节点,放到最后,然后与其父节点比较,如果大于父亲节点,则交换;沿着往根的路径比较交换下去,直到遇到不交换或者根节点为止。
 *  方式2, 从上往下堆化 
 *
 *  情况2, 如果数据是给定的一个数组,则可直接在原数组上进行堆化。
 *  思路:
 *  从倒数的第一个非叶子开始往前遍历, 往下堆化,直到不能交换或者到叶子节点为止。
 *  画图理解
 *
 *
 *  堆排序
 *  思路
 *  交换根元素(最大值)和最后一个元素,然后从根往下进行一次堆化,注意需要排除当前最后一个元素。重复遍历下去,直到最后一个元素为止。
 *  此时二叉树的中序遍历,也就是数据的下标顺序就是从小到大排序的。
 *  画图理解
 *
 */
public class MaxHeapTree {
   

    int[] data; //堆数据,完全二叉树,用数组表示, 注意data[0]无意义,根节点从下标1开始。
    int lastIndex; //最后一个节点的索引位置。

    public MaxHeapTree(int[] data) {
   
        this.data =
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值