今天我们主要讨论所谓的平摊分析(amortized analysis),它是用来分析一系列操作的平均所需要的代价。可能有人会问它利用概率论的知识,通过概率来求平均情况。答案是否定的,它并不涉及概率。在一些情况下平摊分析能够很好的帮助我们分析我们程序的代价或者消耗。
这部分主要有三个内容,如图所示:
1. 聚合分析(aggregate)
聚合分析比较简单。它就是执行n次操作的最坏情况(worst case)下时间为
T(n)
, 所以平均时间为
T(n)/n
. 接下来我们用一个示例展示一下。
1.1 栈操作
我们都知道栈的push() 和pop() 操作的代价都是
O(1)
, 如果它们共同操作n次的话,真实时间(actual time)
Θ(n)
.这时我们将其平均化则只有
O(1)
.
2. 核算法(The accounting method)
核算法是针对不同的操作有不同的平摊分析,同时我们利用平摊代价作为整个操作的上界。而
总平摊代价=总实际代价+所存储的信用(credit)
且信用的定义是这样的:当一个操作的平摊代价超出其实际代价时,我们将差额存入数据结构中的特定对象,存入的差额就是信用[1]。同时我们用
c^i
表示平摊代价, 而用
ci
表示实际代价,且我们认为信用不为负数,所以我们有这样的关系
∑i=1nc^i≥∑i=1nci(2.1)
下面我们直接引入例子:
2.1 栈操作
push()操作平摊代价为2, 1个用来真实操作的代价,而另一个1用来从当信用(credit), 而信用用来支付未来某些操作的真实代价(如pop()操作)。
pop()操作平摊代价为0
通过公式
2.1
的计算我们可以算出一个先push()操作
p
次,后pop()操作q次,且
q+p=n(p≥q)
,我们可以计算出它的平摊时间和实际时间:
总的平摊时间:∑i=1nc^i=2p信用(credits):(p−q)实际时间:∑i=1nci=∑i=1nc^i−credits=2p−(p−q)=p+q=n又因为:p>q,所以:n/2≤p≤nso:n≤总的平摊时间≤2n
所以综上总的平摊时间和实际时间都是O(n)
3. 势能法(The potential method)
势能法和我们上述描述的核算法不同的地方在于它针对的是整个数据结构,而不是特定的数据操作。它像我们高中学的物理势能差不多,当势能升高,存储的势能可以用来为将来的操作进行付费从而势能降低。对于最初的数据结构我们用
D0
表示,而在第
i
次操作后的数据结构状态为Di表示。
同时在这里我们用
c^i
表示第
i
次操作的平摊代价, 用ci表示第
i
次操作的真实代价,而用Φ(Di)表示第
i
次操作后的总的势能。
而至于每次的平摊代价为:
c^i=ci+Φ(Di)−Φ(Di−1)(3.1)
所以我们的总的平摊代价为:
∑i=1nc^i=∑i=1n(ci+Φ(Di)−Φ(Di−1)) =∑i=1nci+Φ(Dn)−Φ(D0)(3.2)
我们将
3.2
的公式变形可得实际代价:
∑i=1nci=∑i=1nc^i−Φ(Dn)+Φ(D0)(3.3)
由于整个数据结构的势能是不能为负的,且我们认为
Φ(D0)=0
, 所以很容易得:
总的平摊代价≥总的真实代价也就是:∑i=1nc^i≥∑i=1nci(3. 4)
下面我用例子详细分析势能法:
3.1栈操作
push()
的真实代价为
1
,且会增加1个势能
pop()
的真实代价为
1
, 且会减少1个势能
现有
p
个push()操作和
q
个pop()操作,且
p+q=n,p≥q
w我们先利用公式
3.2
求其总的平摊代价:
∑i=1nc^i=∑i=1p(ci+Φ(Di)−Φ(Di−1))+∑i=p+1n(ci+Φ(Di)−Φ(Di−1)) =2p+0=2p(3.5)
可能会有人会问为什么我们不用公式
3.2
的第
2
个公式来算,而只是用最原始的公式算?那是因为如果我们用第
2个公式,能解决许多问题的话,我们就不需要平摊分析了(因为我们利用平摊分析是来计算真实代价的,第2个式子的总的真实代价需要我们知道的)。这是因为在许多问题中, 每次的平摊代价很容易求, 而真实代价却异常难。
在解释一下:当
0≤i≤p
时,即每次的
push()
操作的平摊代价为
c^i=1(actualcost)+1(potential)=2
;当
p+1≤i≤n
时,即每次的
pop()
操作的平摊代价为
c^i=1(actualcost)(−1)(potential)=0
e而第n次操作后总的势能:
Φ(Dn)=p−q(3.6)
j将公式
3.5
和
3.6
代入到
3.3
中去得:
∑i=1nci=2p−(p−q)=p+q=n
所以
n
次的
push()和
pop()
操作的真实代价是
O(n)
, 也不难求出总平摊代价为
O(n)
.
4. 总结
1)为什么叫聚合分析(aggregate analysis)?
那是因为它把n操作的总代价加起来为
f(n)
, 而平摊到每次的操作则为
f(n)/n
.
2 )为什么叫做核算法(the accounting method)?
首先我们考虑只有先有
push()
操作,然后才会有
pop
操作, 所以我们把以后可能执行的
pop()
操作所需要的代价当做
信用(credit)
存储起来,当我们实际当中执行
pop()
操作时,我们就用我们预先的信用来支付就行了。
3 ) 为什么叫是能法?
用上面最初的高中物理势能知识理解。
4)核算法和势能法都是将
总的平摊代价
作为
真实代价
的最大
上界
。当两种方法都求解不真实的代价的话,我们只好还有一个上界值得我们去考虑(往往这个上界对我们的程序分析往往很有用)。
感谢
j本文是基于《算法导论》写的,最主要的是有本人大量的心得体会,感谢《算法导论》的那些作者Thomas H.Cormen、Charles E.Leiserson等 人。如果有错误的请留言,不甚感激。谢谢。
参考
[1] 《算法导论》Thomas H.Cormen、Charles E.Leiserson等 第三版第17章 “摊还分析” p261