在摊还分析中,我们求数据结构的一个操作序列中所执行的所有操作的平均时间,来评价操作的代价。这样,就可以说明即使一个序列的某个操作很复杂,平均代价还是很低的。
摊还分析中最常用三种技术:
聚合分析,这种方法用来确定一个n个操作的序列的总代价的上界T(n),因而每个操作的代价是T(n)/n,将平均代价作为每个操作的摊还代价
核算法,将某些较早操作的“余额”作为“预付信用”储存起来,与数据结构中的特定对象相关联,在操作序列的随后部分,储存的信用即课为那些缴费少于实际代价的操作支付差额
势能法,也是分析每个操作的摊还代价,也是通过较早操作的余额来补偿稍后操作的余额的差额,但是不是针对对象的,而是将势能作为整体储存
好吧,这样说起来很抽象,通过一个栈操作的例子来说明
普通的栈操作包括PUSH和 POP, 时间复杂性均是O(1),这里加一个额外的MULTIPOP(S,k)操作,删除栈S的从栈顶开始的k个元素(栈不空的情况下),这个操作的总代价是min(s,k)(退不到K个就栈空了,那就只退s个)。
聚合分析
一个由n个PUSH POP MULTIPOP组合的操作序列在一个空栈上的执行情况,序列中一个MULTIPOP操作的最坏情况代价的O(n),因为栈的大小最大为n,因此一个栈操作的最坏情况是O(n),从而n个操作的最坏情况是O(n2)。但是这不是一个确界。通过使用聚合分析,我们知道其实在一个空栈上进行一系列N个操作最多代价是O(n),因为当一个对象压入栈后,最多将其弹出一次,因此对一个栈来说,能执行的POP次数,包括MULTIPOP中POP次数,都只能最多是PUSH的次数,所以上限其实是O(n)
核算法
对不同操作赋予不同费用,将这个费用称为其摊还代价。当一个操作的摊还代价大于其实际代价时,将差额存入数据结构中的特定对象,存入的差额称为信用。当然要确保信用是非负值,否则总体的摊还代价就会小于实际代价,求的就不是上界了。还是用上面的栈操作为例:
操作的实际代价是:
PUSH 1
POP 1
MULTIPOP min(s,k)
s是调用栈的规模,k是参数
赋予这些操作如下摊还代价
PUSH 2
POP 0
MULTIPOP 0
在此例中,所有摊还代价都是常数。假定使用一美元表示一个单位的代价,从一个空栈开始,存入一个数据就相当于给出了代价2,但是实际只用话费1美元,那么剩下的一美元就存起来,等到出栈的时候,这一美元正好用来当出栈的代价,相当于之前的两美元是用来支付了入栈的代价同时预付了出栈的代价,所以在POP的时候可以不缴纳任何费用,MULTIPOP也是一样的。换句话说,每个数在入栈的时候都是已经有了一美元的出栈费,所以出栈相关的操作无需在缴纳了。
势能法
势能法摊还分析并不将预付代价表示为数据结构中特定对象的信用(数的出栈费),而是表示为势能或简称势。
我们将对一个数据结构D0执行n个操作(比如栈),对每个i = 1,2,。。。n, ci为第i个操作的实际代价,令Di为在一个数据结构Di-1上执行第i个操作得到的结果数据结构。势函数F将每个数据结构Di映射到一个实数F(i)(感觉就像物理上每个位置对应一个重力势能一样),那么第i个曹组的摊还代价Ci = ci + F(Di)-F(Di-1),也就是摊还代价等于实际代价加上势能差,所以总摊还代价就是 总实际代价+F(Dn)-F(D0) (是不是就像重力作用下势能变化,只跟起始位置和初始位置的势能值有关?)
再次回到上面的栈操作, 设F(D0) = 0,因为栈中对象永远不可能为负,因此F(Di)>=0=F(D0).
如果第i个操作是PUSH操作,此时包含s个对象,则 F(Di)-F(Di-1)=(s+1)-s = 1
所以PUSH操作的摊还代价是Ci= ci + F(Di)-F(Di-1) = 2; 类似的POP的代价是0