「闲话随笔」势能分析法
点击查看目录
这闲话已经被催了两天了,累死我了。
感谢 joke3579 帮我找到了 Tarjan 的论文。虽然没看懂只截了一下里面的图。
语文考了 82,需要单独给语文老师发作业,很闹心。
今日推歌:盲龙默虎 feat.洛天依 vs 言和。
iKz 老师的《一年一度武斗大赛》系列是不是快要更了?
简介
一种挺神奇的分析时间复杂度的方法。
一个算法/数据结构单次操作的复杂度难以计算时可以用势能分析法。
分析
设第 i 次操作的时间复杂度为 ai,显然总复杂度为 ∑ni=1ai。
构造一个势能函数 ϕ(i) 表示第 i 次操作后的势能,设 Δϕ(i) 表示单次的势能变化,即 Δϕ(i)=ϕ(i)−ϕ(i−1)。
设摊还代价 bi=ai+Δϕ(i),则:
n∑i=1ai=(n∑i=1ai+Δϕ(i))−n∑i=1Δϕ(i)=n∑i=1bi+ϕ(0)−ϕ(n)
不知道我在说什么,对不对?看起来啥用没有,对不对?
不对就怪了
实际上我们虽然无法算出具体的 ai,但是可以用未知数表示出来,这个时候构造一个优秀的势能函数,就可以巧妙地将 bi 化为一个数或是求出其上限。
看看例题就都明白了。
例题
二进制计数器
题意:
一个二进制下的计数器,每次累加 1,做 n 次累加,求操作的次数。
定义一次操作为一位上发生变化,因此每次累加 1 可能会带有多次操作。
设每次累加会有 x 位 1 变为 0,那么 ai=x+1(还会有一个 0 变为 1)。
那么构造势能函数 ϕ(i) 表示累加 i 次后数字里有多少个一。
显然 Δϕ(i)=1−x。
然后就发生了一件神奇的事:bi=(x+1)+(1−x)=2!
然后化简原式得到复杂度 2n−ϕ(n)≤2n。
单调栈
不会单调栈就来学这个是不是有点过猛了。
显然单调栈不用这么麻烦地分析,但确实可以这么用。
设每次操作会有 x 个元素弹出,1 个元素加入,那么 ai=x+1。
那么构造势能函数 ϕ(i) 表示当前栈内元素个数。
显然 Δϕ(i)=1−x。
然后就发生了一件神奇的事:bi=(x+1)+(1−x)=2!
然后化简原式得到复杂度 2n−ϕ(n)≤2n。
然后还发生了一件神奇的事:这个过程和上个过程居然没啥区别。
Splay
默认读者已经会 splay 了,不会的话去网上搜 或者等我平衡树学习笔记写完了再看
定义 x 为一棵 splay 上的一个节点,x′ 为 x 旋一次后的位置,|x| 为以 x 为根的子树的大小,ϕj(x)=log2|x| 为一次 splay 操作中第 j 次操作后节点 x 的势能,Φj 为一次 splay 操作中第 j 次操作后整棵树的势能。
然后我们开始分析三种旋转情况的 ai:
-
旋上根(zig):

可以发现转一次只会有 x 和 y 的势能发生变化。
显然 ϕj(x)=ϕj−1(y)。
bzig=aj+ΔΦj=1+ϕj(x)+ϕj(y)−ϕj−1(x)−ϕj−1(y)=1+ϕj(y)−ϕj−1(x)≤ϕj(x)−ϕj−1(x)
-
三点共线(zig-zig):

可以发现转一次只会有 x,y 和 z 的势能发生变化。
显然 ϕj(x)=ϕj−1(z)。
bzig-zig=aj+ΔΦj=2+ϕj(x)+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)−ϕj−1(z)=2+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)
注意到这个 2 长得很难看,尝试把它去掉。
不难发现 |x|+|z′|+1=|x′|,因此 |x′|2>4⋅|x|⋅|z′|,那么:
ϕj−1(x)+ϕj(z)−2ϕj(x)=log2|x|⋅|z′||x′|2≤log214=−2
然后代入一下原式:
bzig-zig=2+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)=2ϕj(x)−ϕj−1(x)−ϕj(z)+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)=2ϕj(x)−2ϕj−1(x)+ϕj(y)−ϕj−1(y)≤3(ϕj(x)−ϕj−1(x))
这样就求出了它的上限。
-
三点不共线(zig-zag):

比较像上面的式子。
bzig-zag=aj+ΔΦj=2+ϕj(x)+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)−ϕj−1(z)=2+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)
然后由于 |y′|+|z′|+1=|x′|:
ϕj(y)+ϕj(z)−2ϕj(x)=log2|y′|⋅|z′||x′|2≤log214=−2
然后代入原式:
bzig-zag=2+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)=2ϕj(x)−ϕj(y)−ϕj(z)+ϕj(y)+ϕj(z)−ϕj−1(x)−ϕj−1(y)=2ϕj(x)−ϕj−1(x)−ϕj−1(y)≤2(ϕj(x)−ϕj−1(x))
现在我们把三种旋转的摊还代价算出来了,问题变成了如何算出一次 splay 操作的摊还代价。
显然一次 splay 操作会进行若干次 zig-zig, zig-zag 和至多一次 zig,且三者上限都不超过 3(ϕj(x)−ϕj−1(x)),那么:
bi≤k∑j=13(ϕj(x)−ϕj−1(x))=3(ϕk(x)−ϕ0(x))=O(log2|x′||x|)≤O(log2n)
然后由于 0≤Φi≤nlog2n,−nlog2n≤Φ0−Φn≤nlog2n。
所以总复杂度为:
m∑i=1ai=m∑i=1bi+Φ0−Φn≤O(mlog2n)+O(nlog2n)=O((m+n)log2n)
The End
本文作者:K8He
本文链接:https://www.cnblogs.com/K8He/p/chat_20230112.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步