STL序列式容器之heap(注heap并不归属于stl组件)

在学习优先队列之前,我们有必要学习一下堆,heap。

注:heap并不归属于STL组件,它是幕后英雄,扮演则优先队列priority_queue的助手。我们知道优先队列可以将元素以任意次序压入队列中,但是弹出队列元素时,总是弹出优先级最高的元素。最大堆就满足这样的性质。

1、优先队列为什么要使用堆(最大堆或者最小堆)作为底层机制?

(1)如果我们选用链表list作为优先队列的底层机制。

list的插入可以享用常数时间,但是想要查找到list的极值,则需要查找整个list。查找操作为O(n)

换一种做法,假如插入之前list已经经过排序了,那么取得极值和删除操作将会达到常数时间,但是插入呢?插入将是O(n)。

2、使用二叉搜索树(例如红黑树)作为底层机制?

这样元素的插入和取极值都只要O(logn)的时间复杂度。但是首先二叉搜索树需要足够的随机性,其次二叉搜索树并不容易实现,杀鸡用牛刀,小题大做了。优先队列的底层结构复杂度应该在list和二叉搜索树之间。

3、堆的隐式表述法

注:堆是一棵完全二叉树(注意和满二叉树的区别)

堆是一种数据结构,那么在计算机中如何进行表示呢?给定一个完全二叉树,我们可以用数组进行记录,如父节点的序号为i,那么其左右孩子的节点将分别是2i,2i+1。(假设根节点从序号1开始),这种用数组表示树的方法称为隐式表述法。

但用一般的数组无法进行动态生长,因此可以使用vector记录树中的元素。

注:STL中默认提供的是最大堆(父节点大于子节点),为了实现最小堆,可以自定义比较函数。

4、比较重要的几种heap算法

(1)push_heap算法

如何压入元素并使得heap仍然服从最大堆呢(这里我们只讨论最大堆,最小堆的原理一样)?
首先插入的元素应该位于底层结构vector的end()出,然后和它的父节点不断比较(回溯),如果比父节点大,那么进行对换,否则插入完成。

需要十分注意的是STL中的push_heap操作,必须将元素插入到vector尾部之后才进行push_heap操作,否则结果是不可预料的。

vector<int> res=...;

res.push_back(新元素);

push_heap(res.begin(),res.end());

从push_heap的参数可以看出,其只接受两个迭代器作为实参。

(2)pop_heap算法(调整堆)

弹出最大元素就位于根位置,此时需要用堆中最后一个元素弥补根元素,然后将新的根元素和其较大子节点进行对调,不断下溯,直到叶节点为止。然后在执行一次回溯(和push_heap中的一样,采用一次回溯的原因是可能这个值比现在的两个子节点都大,试着自己画个图理解一下)——这是STL采用的调整堆的方法。

实际上,我们自己实现最大堆时,将最后一个元素放入根位置时,就直接比较这个值和其子节点的值,如果其值比两个节点都大,则不用调整,否则将根位置和较大的子节点进行对换。由于这里多了一层比较,所以我们不用回溯过程。

注:STL执行pop_heap操作之后,最大元素只是被放在底层容器的最尾端,尚未被取出,如果要取其值,可用vector的back函数实现,如果要弹出,则pop_back()函数。

因此,当我们用一个vector和make_heap构造一个堆时,并进行pop_heap操作,此时若未弹出最后一个元素,那么这个对已经不是最大堆了,我们不能够再次直接执行pop_heap(),因为pop_heap执行的对象是最大堆,即直接找出最大值放入vector的尾部(或者改变pop_heap的迭代器参数范围)。但是若弹出最后一个元素,剩下的元素仍然服从堆的性质。

pop_heap只接受两个迭代器作为实参。

(3)sort_heap算法(排序)

既然每一pop_heap都将最大值放到vector的末尾,那么如果每次都缩小pop_heap的参数范围(从后向前缩减一个与元素),那么最终得到的vector将是一个递增序列。

sort_heap也是接受两个表示范围的迭代器参数。最后得到的vector已经不满足堆的性质了。

(4)make_heap算法(构造堆)

给定一个数组,如何构造其称为堆呢?为了实现这个功能,需要调整对,在pop_heap中已经用到了调整堆的算法了。

这里拷贝一幅图(注意是最小堆)进行说明,如果侵权请告知,将立即删除。


首先,未处理的数组对应的堆为图1模样。从第四个节点开始([8/2]=4)——从第一个非叶节点开始,因为50 < 97,故要交换两节点,交换后还要继续对其新的左子树进行类似输出后那样的筛选。易见其左子树只有节点97,已经为最佳情况,故可以继续堆的初始化,如图2。再考虑第三个节点,因为13 < 27 < 65,即节点13为当前的最小节点,故与节点65交换,并对新的左子树进行筛选,其也为最佳情况,故可继续堆的初始化,结果如图3。然后考虑第二个节点,因为38 < 50 < 76,故已经为最优情况,不用调整。最后再考虑第一个节点,根节点。因为 13 < 38 < 49,故需要将根节点49与其右孩子节点13交换,交换后还要继续对其新的右子树进行类似输出后那样的筛选,可见右子树还需要调整,因为 27 < 49 < 65,故将节点49与节点27交换。此时已经处理完了根节点,初始化结束。最终结果如图5.

每次都是将检查节点和子节点比较,选择三者当中最小的一个,交换完之后还有检查被交换的节点是否满足堆得性质,从而继续进行交换。

sort_heap和make_heap的参数也是两个迭代器。

5、由于heap输出最大值或者最小值,因此heap是没有迭代器的。

6、要使用STL的heap,要包含头文件#include<algorithm>,因为heap本身不是STL的组件,它只提供了一系列算法。

7、push_heap的时间复杂度为O(h),pop_heap的时间复杂度为O(h),sort_heap的时间复杂度为O(nlogn),make_heap的时间复杂度也为O(nlogn)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值