2008年12月24日星期三

平摊分析 (amortized analysis)

在平摊分析中,执行一系列数据结构操作所需要的时间是通过执行的所有操作求平均而得出的。平摊分析可以用来证明在一系列操作中,通过对所有操作求平均之后,即使其中单一的操作具有较大的代价,平均代价还是很小的。

平摊分析不牵涉到概率, 平摊分析保证在最坏情况下,每个操作具有的平均性能。

三中最常用的技术:

1.聚集分析(aggregate analysis)

证明对所有的n, 由n个操作所构成的序列的总时间在最坏情况下为T(n)

例子:

栈操作

PUSH(S, x):将x压入S

POP(S):弹出栈顶

MULTIPOP(S, k):弹出栈顶k个对象

由n个PUSH, POP和MULTIPOP操作序列,其作用于一个初始为空的栈。

分析:每个操作的最坏情况时间是O(n). 因此n个操作的序列的代价是O (n^2)

虽然这一分析正确,但这个界不够紧确,利用聚集分析,可以获得更好的上界。

一个对象在每次被压入栈后,至多被弹出一次。所以,调用POP(包括MULTIPOP)的次数至多等于PUSH的次数,即至多为n.。对任意的n,包含n个PUSH, POP, MULTIPOP操作的序列的总时间为O(n). 每个操作的平均代价为O(n) / n = O(1).。 聚集分析中,将每个操作的平摊代价指派为平均代价。所以三个栈操作的平摊代价都是O(1)。



2.记帐方法

在平摊分析的记帐方法中,对不同的操作赋予不同的费用,某些操作的费用比它们的实际代价或多或少。

我们对一个操作的收费的数量称为平摊代价。当一个操作的平摊代价超过了它的实际代价时,两者的差值就被当作存款(credit),并赋予数据结构中的一些特定对象,可以用来补偿那些平摊代价低于其实际代价的操作。这种方法与聚集分析不同的是,对后者,所有操作都具有相同的平摊代价。

数据结构中存储的总存款等于总的平摊代价和总的实际代价之差。注意:总存款不能是负的。



例子:栈操作

实际代价:

PUSH 1

POP 1

MULTIPOP min(k, s)

现在对它们赋予以下的平摊代价:

PUSH: 2

POP: 0

MULTIPOP 0

假设用1元钱来表示代价的单位。开始时栈时空的。

栈数据结构与餐厅中一堆叠放的盘子类似。当把一个盘子压入栈时,用1元钱来支付该动作的实际代价,还有1元的存款,将其放在盘子的顶上。盘子上面的钱时用来预付将盘子从栈中弹出所需代价的。当执行一个POP操作时,对该操作不收取任何费用,只需用盘中存放的存款来支付其实际代价即可。同理,也无需对MULTIPOP收取任何费用。

因为栈上的每个盘子上面都有1元钱,而且栈中总有非负个数的盘子。这就保证了存款的总量总是非负的。这样,对任意的包含n次PUSH, POP, MULTIPOP操作的序列,总的平摊代价就是总的实际代价的一个上界。总的平摊代价为O(n), 故总的实际代价也为O(n).



3.势能方法

在平摊分析中,势能方法(potential method)不是将已预付的工作作为存在数据结构特定对象中存款来表示,而是表示成一 种“势能”或“势”,它在需要时可以释放出来,以支付后面的操作。势是与整个数据结构而不是其中的个别对象发生联系的。

对一个初始数据结构D0 执行n个操作。对每个i,设ci为每个操作的实际代价,

Di 为对数据结构Di-1执行第i个操作的结果. 势函数Φ 将每个数据结构Di 映射为一个实数Φ(Di), 即与Di相联系的势. 第i个操作的平摊代价ai根据势函数Φ 定义为

ai = ci + Φ(Di) - Φ(Di-1)

每个操作的平摊代价为其实际代价ci加上由于该操作所增加的势。

n个操作的总的平摊代价为

∑ai = ∑ci +Φ(Dn) - Φ(D0)

如果我们能定义一个势函数Φ使得对所有n,有Φ(Dn) ≥ Φ(D0), 则总的平摊代价∑ai就是总的实际代价∑ci的一个上界。通常为了方便起见定义Φ(D0) = 0。

直观上看,如果第i个操作的势差Φ(Di) - Φ(Di-1)是正的,则平摊代价ai表示对第i个操作多收了费,同时数据结构的势也随之增加了。如果势差是负值,则平摊代价就表示对第i个操作的不足收费,这是通过减少势来支付该操作的实际代价。

平摊代价依赖于所选择的势函数Φ。不同的势函数可能会产生不同的平摊代价,但它们都是实际代价的上界。最佳势函数的选择取决于所需的时间界。



例子:栈操作

定义栈上的势函数Φ为栈中对象的个数。开始时要处理的空栈D0,且Φ(D0)=0。因为栈中的对象数始终时非负的,故在第i个操作后,栈Di就具有非负的势。

以Φ表示的n个操作的平摊代价的总和就表示了总的实际代价的一个上界。

现在计算各个操作的平摊代价。

如果作用于一个包含s个对象的栈上的第i个操作是PUSH:

则势差为Φ(Di) - Φ(Di-1) = (s+1) – s = 1

这个PUSH操作的平摊代价为ai = ci + Φ(Di) - Φ(Di-1) = 1+1=2

假设栈上的第i个操作是MULTIPOP(S, k). 该操作的实际代价为min(s, k).

势差为Φ(Di) - Φ(Di-1) = - min(s, k)

因此MULTIPOP操作的平摊代价为

ai = ci + Φ(Di) - Φ(Di-1) = min(s, k) - min(s, k) = 0

类似POP操作的平摊代价也是0。

三中栈操作的每一种的平摊代价都是O(1), 这样包含n个操作的序列的总平摊代价就是O(n)。已经证明n个操作的平摊代价的总和是总的实际代价的一个上界。所以n个操作的最坏情况代价为O(n).



例子:

动态表,当向满的表插入一项时,将表扩大一倍,但当删除一项而引起表不足1/4满时(不是1/2),就将表缩写为原来的一半。

可以利用平摊分析,证明插入和删除操作的平摊代价均为O(1).


-- <<算法导论>> 笔记

2 条评论:

Wei 说...

很好的笔记

Wei 说...

美中不足是部分翻译有些生硬