2018暑假集训专题小结 Part.4

版权声明:本人初三蒟蒻一只,欢迎各位大佬来点评。 https://blog.csdn.net/HiChocolate/article/details/81435933

各种堆 √

这个堆就是普普通通的堆,太简单了,以至于我不想讲。
就只介绍介绍支持的操作——
加入一个新点。
查询最值。
删除堆顶的点。
好像就没啦。

可合并堆(左偏树)

这个左偏树特别厉害,是堆的一个神奇版。(废话)
首先,他支持堆的所有操作,并且支持合并两个堆,还有,左偏树是二叉树(堆)。
实际上,这个左偏树就是在支持合并的基础上进行堆的操作。

那么我们就来看看左偏树怎么合并的。
首先我们来看看左偏树记录最主要的一个东东——
len[]
这个len的定义是:该节点到 有空的子结点的结点 的距离
有点绕,不妨看看图:
这里写图片描述
我们可以发现一个神奇的性质——
len[i]=min(len[left_son[i]] , len[right_son[i]])+1
当且仅当左右都有儿子,否则就:
len[i]=0

然后有这个len,我们就可以说明左偏树的性质了——
左偏树就是对于任意一个点,有两个儿子。
他们满足左边儿子的len永远大于右边儿子的len值。
这就是左偏树的性质。

我们有这个东西,我们就可以考虑合并了。
这里写图片描述
这两个堆要合并(数字表示权值)
由于合并就是一个堆插入到另一个堆中,
那么我们首先就把堆顶小的看作“被插入堆”(左边),大的看做“插入堆”(右边)。

然后,我们就对左边的进行插入。
这里写图片描述
我们首先看左边的堆的右子树,然后,我们发现,右边的堆可以交换到左边的堆的右子树,那么我们就强行把左边的堆的右子树给提出来,与右边的堆直接交换——
这里写图片描述
那么就交换过来啦~ (画图神技)
然后我们把左边的堆的右子树看做是新的左边堆。
然后,我们递推重复上面的操作——
这里写图片描述
交换:
这里写图片描述
最后变成这个样子——
这里写图片描述
要注意的一个地方是
如果右边的堆的顶大于左边堆的右儿子,那么就不交换,继续递归。

当然,这样做不免会破坏原来的len值,于是我们就在回溯的时候更新即可,然后再通过交换左右儿子的方法来维护左偏树的性质即可。

这样就是一次交换,时间复杂度大约是log的。
那么我们来看看如何维护普通堆支持的操作。

加入一个新点:把原来的左偏树插入一个新的节点即可。
查询最值:直接查询。
删除堆顶的点:合并当前左偏树的根的左右子树即可。

是不是有点强大?
一个问题:
会不会一直加入一个点导致右边有一条特别长的链?
A:不会。因为我们是时时刻刻维护len值的,所以当右边很长的时候,就会交换到左边去,于是就很平衡了。
一个问题:
比平衡树平衡吗?
A:不知道。(这个问题可能没有意义)

空间复杂度(n)
时间复杂度(所有操作都是log n)

斜堆

这个则是左偏树(可合并堆)的一个简化版。
它的具体方法就跟左偏树差不多。

流程:
1、判断左边堆的右子树与右边堆的大小关系。
2、交换。
3、回溯,不看len标号,直接左右子树交换。

所以说,左偏树与斜堆的主要区别就是没有len标号。
斜堆不用len标号,然后就是对于每个点都把左右子树交换。
以此来维持“左偏”
然而,没有你想象中的那么偏。

其实它没有严格的像左偏树那样维持“左偏”。
所以,是有办法把这种做法的右子树变成很长很长的。
虽然很长,但也就是会把你的栈给卡爆。

事实上,我不会如何把它卡爆

有一种非递归的版本——(很好理解,但很难证)
首先,把每颗树的右子树给切出来。(分离出来)
然后,就会变成这棵树有根,有左子树或没有,还有一个新的树(切出来的右子树)。
继续在这个新的树继续切右子树。
百度上有图,我来转转(以下的图都是转百度的)
原图——
这里写图片描述
然后切一切,之后,我们就按照每颗树的顶点拍序——
这里写图片描述
把最右边的树连接到它左边的树的右子树,然后把这棵树左右子树交换——
这里写图片描述
依次这样做:
这里写图片描述
这里写图片描述
最后变成——
这里写图片描述
这就是非递归版的斜堆。
其实,证明正确性也是很好感性理解的。

当然,它可以做很多左偏树的东西(几乎都可以)。
时间复杂度也是一样的。
当然,常数小,比较推荐!

——————————————————————————
下次再更:二项堆、斐波那契堆、配对堆
——————————————————————————

猜你喜欢

转载自blog.csdn.net/HiChocolate/article/details/81435933