势能分析法

势能分析法

势能分析通过定义一个势能函数(通常表示为 \PhiΦ),度量数据结构的潜在能量,即系统状态中的预留资源,这些资源可以用来支付未来的高成本操作。势能的变化用于平衡操作序列的总成本,从而确保整个算法的均摊成本在合理范围内。

——摘自 OI-wiki

原理

定义状态 S 为某一时刻数据结构的状态,初始状态为 S0

其次,定义的势能函数 Φ(S) 需要满足以下两个性质:

  • 初始势能:Φ(S0)=0
  • 非负性:任意状态下 Φ(S)0

Ps:也有说法表示不必满足以上性质,势能函数反映数据结构变化量即可。

对于每个操作,均摊成本 c^ 定义为

c^=c+Φ(Si)Φ(Si1)

其中 c 为这次操作所需的实际复杂度。

该公式表明均摊成本等于实际成本加势能变化量。

有一种理解方式是:当 c 小时,势能增加 Φ(Si)>Φ(Si1);当 c 大时,势能减小 Φ(Si)<Φ(Si1)

我们通过势能函数来分析一系列操作的总成本。

i=1nci=i=1nci^Φ(Sn)+Φ(S0)

例题:动态数组的扩容分析

题面

初始时数组大小为 0,第一次加入数组大小会变为 1。之后每次加入时,如果数组大小不够,会新开一个大小为原来两倍的数组,在新数组中依次插入原来的数据与当前插入的数。

若不计开新数组的时间,单次插入 O(1),分析插入 n 个数时的复杂度。

分析

设数组长度为 m,插入的数的个数为 n,设势能函数 Φi=2nm

  1. 插入操作(不扩容)

    操作成本:c=1

    势能变化:ni=(ni1+1),mi=mi1ΦiΦi1=(2ni+mi)(2ni1+mi1)=2

    均摊成本:c^=c+ΦiΦi1=2

  2. 插入操作(扩容)

    操作成本:c=n+1

    势能变化:ni=(ni1+1),mi=2mi1=2ni1ΦiΦi1=(2ni+mi)(2ni1+mi1)=2n

    均摊成本:c^=c+ΦiΦi1=3

此处看出虽然扩容操作成本高,但势能变化量减小;反之同理。整体均摊在 O(1) 级别。

实践:分析 Splay 树的复杂度

x 表示节点 x|x| 表示 x 的子树大小。

定义 Φ(S)=iSlog|x|,证明旋转到根的操作是 logn

zigzigzagzag

势能变化量为

ΦiΦi1=ϕi(x)+ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)ϕi1(z)

由于 ϕi(x)=phii1(z),所以有

(1)ΦiΦi1=ϕi(x)+ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)ϕi1(z)(2)=ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)

根据子树大小关系推导的 ϕ 关系,可知

(3)ΦiΦi1=ϕi(x)+ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)ϕi1(z)(4)=ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)(5)ϕi(x)+ϕi(z)2ϕi1(x)

观察上面的树,不难发现有

|x||x|+|z|

于是我们可以得到(注意与上面的式子不同)

(6)ϕi1(x)+ϕi(z)2ϕi(x)(7)=log|x||z||x|2log|x||z|(|x|+|z|)2(8)|x||z|2|x||z|=log12=1

于是均摊势能为

(9)ci^=ci+ΦiΦi11+3(ϕi(x)ϕi1(x1))+(ϕi1(x)+ϕi(z)2ϕi(x))(10)=1+3(ϕi(x)ϕi1(x1))1(11)=3(ϕi(x)ϕi1(x1))

zigzagzagzig

首先还是分析势能变化量

(12)ΦiΦi1=ϕi(x)+ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)ϕi1(z)(13)=ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)

观察上图,可得

|x||y|+|z|

同理可得

ϕi(y)+ϕi(z)2ϕi(x)1

于是有

(14)c^i=ci+ΦiΦi1(15)ϕi(y)+ϕi(z)ϕi1(x)ϕi1(y)(ϕi(y)+ϕi(z)2ϕi(x))(16)=2ϕi(x)ϕi1(x)ϕi1(y)(17)2(ϕi(x)ϕi1(x))

zigzag

c^i=ci+ΦiΦi1=1+ϕi(x)ϕi1(x)

综上,1.2. 两种操作均摊为 O(ϕi(x)ϕi1(x)),3 的均摊代价是 O(1+ϕi(x)ϕi1(x)),一次 splay 是若干次 1.2. 操作与一次 3. 操作的结合,所以一次 splay 的代价为 O(1+ϕi(x)ϕ0(x))O(1+logn)=O(logn)

由于其他操作都是基于 splay,在势能函数中以常数的形式体现,于是我们可以估计一棵 Splay 树的平均复杂度是 O(logn) 的,现在我们来计算一下

i=1nci=i=1nc^i+ΦnΦ0

根据上面的分析 i=1nc^i 的代价为 O(nlogn)

再根据势能函数的定义,有

nlognΦnΦ0nlogn

代入原式

(18)i=1nci=i=1nc^i+ΦnΦ0(19)O(mlogn)+nlogn(20)=O((m+n)logn)

Q.E.D.

习题

栈问题

一个栈,有以下 3 种操作:

  1. 入栈
  2. 弹出栈顶元素
  3. 在栈顶弹出若干元素

通过均摊分析其复杂度。

二进制加法计算器

有一个二进制加法计算器,在 1111+1 时会改变五个数字的值,修改的数字最多达到 O(logn),假设计算器修改一个位置的复杂度是 O(1) 的,请证明这种计算器执行 +1O(1) 的。

序列 GCD

给出一个有 n 个元素的数列,求他们的最大公约数,采用以下算法:

gcd(a,b) = if b==0 return a;
              else return gcd(b,a%b)
          
int main()
   ans = a[1]
   for(int i=2; i<=n; ++i)
       ans = gcd(ans,a[i])

求这个算法的时间复杂度,并给出证明。

习题解析

栈问题

设势能函数 Φ 为栈中元素个数,分析操作复杂度。

  1. c^=c+ΦiΦi1=1+1=2
  2. c^=c+ΦiΦi1=11=0
  3. c^=c+ΦiΦi1=k+(Φi1k)Φi1=0

仅入栈时均摊复杂度为 O(1),时间复杂度为 O(n)

二进制加法计算器

设势能函数 Φ 为目前计算器中 1 的数目。

  1. 相加不进位

    c^=c+ΦiΦi1=1+1=2

  2. 相加进位

    c^=c+ΦiΦi1=k+(Φi1k+1)Φi1=1

均摊复杂度为 O(1)

序列 GCD

设势能函数 Φ 为当前数的大小,考虑从最终状态转移回初始状态。

  1. 求完一次后 gcd 不改变

    c^=c+ΦiΦi1=1+0=1

  2. 求完一次后 gcd 改变

    c^=c+ΦiΦi1k+2k

由于势能函数小于 a12k 的和小于 a1

时间复杂度为 O(n+logn)

参考资料

均摊复杂度 - OI Wiki

势能分析法 - Dfkuaid - 博客园

时间复杂度-势能分析浅谈 - 洛谷专栏 木木

posted @   彬彬冰激凌  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示