今天我们主要讨论所谓的平摊分析(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)
核算法是针对不同的操作有不同的平摊分析,同时我们利用平摊代价作为整个操作的上界。而
2.1 栈操作
push()操作平摊代价为2, 1个用来真实操作的代价,而另一个1用来从当信用(credit), 而信用用来支付未来某些操作的真实代价(如pop()操作)。
pop()操作平摊代价为0
通过公式
2.1
的计算我们可以算出一个先push()操作
p
次,后pop()操作
所以综上总的平摊时间和实际时间都是O(n)
3. 势能法(The potential method)
势能法和我们上述描述的核算法不同的地方在于它针对的是整个数据结构,而不是特定的数据操作。它像我们高中学的物理势能差不多,当势能升高,存储的势能可以用来为将来的操作进行付费从而势能降低。对于最初的数据结构我们用
D0
表示,而在第
i
次操作后的数据结构状态为
同时在这里我们用
c^i
表示第
i
次操作的平摊代价, 用
而至于每次的平摊代价为:
所以我们的总的平摊代价为:
我们将 3.2 的公式变形可得实际代价:
由于整个数据结构的势能是不能为负的,且我们认为 Φ(D0)=0 , 所以很容易得:
下面我用例子详细分析势能法:
3.1栈操作
push()
的真实代价为
1
,且会增加
pop()
的真实代价为
1
, 且会减少
现有
p
个
w我们先利用公式
3.2
求其总的平摊代价:
可能会有人会问为什么我们不用公式 3.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 中去得:
所以 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