数据结构树论之堆树
堆树,作为二叉树中的一个重要成员,常用于优先队列、TOPK等问题中。
在上一文中,我们使用优先队列非常方便的构建出了赫夫曼树,那么你知道优先队列是怎么实现的呢?
堆树长啥样子,我们先画个图认识一下:(这是大顶堆)
首先,堆树是一颗完全二叉树(完全二叉树的定义你应该还知道吧),同时满足每个父亲节点的值都大于其孩子节点。(每个父亲节点的值都大于其孩子节点的话,就叫小顶堆,为了简化,本文都用大顶堆来举例)
既然堆树是一个完全二叉树,那么我们就可以使用数组来存储,左孩子的下标是父亲节点的2倍,右孩子是2倍+1(根节点的下标从1算起)。
对于一课已经存在的堆树,如何插入一个新的节点?步骤如下
- 新节点插入到最末尾
- 新节点从下往上堆化
插入到最末尾容易理解,从下往上堆化是个什么逻辑?这个画个图来理解就非常容易了
比如,上图堆树中新插入6,第一步,将6插入到末尾;然后新节点6和其父亲节点比较,如果大于父亲节点,则和父亲节点交换;然后继续和上一层的父亲节点比较,直到不再交换或者比较到根为止。这个不断和父亲比较和交换的动作,就叫向上堆化。
理解了这个插入的过程,删除元素的核心流程一样,删除分两种情况:
- 如果删除的是最后一个节点,那么直接删除
- 删除非最后一个节点时,首先将删除节点和最后一个节点交换,然后执行一次向下堆化;删除最后一个节点即可。
同样,删除画个图理解一下
要删除节点10,首先,将节点10和最后一个节点6交换:
然后对6从上到下进行一次堆化,即和两个孩子比较,如果最大的那个孩子比自己还大,则和最大的孩子交换,示例中的就是6和8交换:
继续往下比较和交换,直到不发生交换,或者交换到叶子节点为止
最后,将节点10删除即可。
通过上面的分析,插入和删除的核心就是堆化,比较和交换的次数最多为logn,因此插入和删除的时间复杂度为logn。
明白了插入和删除,那么构建呢,如果从0开始一个元素一个元素的插入构建,相信大家已经会了;但是如果给你一个数组(也就是一个完全二叉树),如何就在本数组完成堆化呢?
这个过程可以从后往前进行向下堆化即可,从倒数第一个非叶子节点开始即可,因为叶子节点的向下堆化(和孩子比较)是没有任何效果的。同样,图示一波
倒数第一个非叶子节点是5, 它向下堆化后,保持原位。第二次,往前一个位置,对7进行向下堆化,它会和10进行交换;再往前对4进行向下堆化,它会和10交换位置。
因为发生了交换,同时还没有到叶子节点,因此继续往下堆化,与8发生交换。
到此就完成了数组的堆化过程,是不是很巧妙。
可以看出,堆树上的核心操作都和堆化离不开,一定要理解这个堆化的过程。
现在我们可以使用堆树来实现自己的优先队列了,添加元素对应插入,弹出元素就是删除根节点。
在排序算法的两篇文章中,还有一种排序算法没说,那就是堆排序,下面我们来看看堆排序是个什么逻辑。
你应该想到,核心依然是堆化,那么整个流程是怎样的呢?
- 将根和最后一个节点交换,也就是最大的元素跑到最后去了
- 然后从根开始往下进行一次堆化(注意堆化的过程排除已经排好序的),完成之后根就变为第二大的元素了
- 重复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 =