算法导论笔记:17摊还分析

来源:互联网 发布:mac有格式工厂吗 编辑:程序博客网 时间:2024/04/29 19:26

       在摊还分析中,通过求数据结构的一系列的操作的平均时间,来评价操作的代价。这样,即使这些操作中的某个单一操作的代价很高,也可以证明平均代价很低。摊还分析不涉及概率,它可以保证最坏情况下每个操作的平均性能。

 

       摊还分析有三种常用的技术:

       聚合分析,它确定n个操作的总代价的上界为T(n),所以每个操作的平均代价为T(n)/n。每个操作都有相同的摊还代价。

       核算法:分析每个操作的摊还代价,不同于聚合分析,每种操作的摊还代价是不同的,核算法将序列中较早的操作的余额作为“信用”储存起来,与数据结构中的特定对象相关联,在随后的操作中,储存的信用可以用来进行支付。

       势能法,与核算法类似,也是分析每个操作的代价,但将势能作为一个整体存储,而与数据结构中的某个对象无关。

一:聚合分析

       聚合分析是证明n个操作的最坏情况下的总时间为T(n),因而每个操作的平均代价(聚合代价)为T(n)/n。所以,聚合分析中的每个操作的聚合代价都是相同的。下面以栈操作和二进制计数器为例说明:

      

       1:栈操作

       栈操作有PUSHPOP,两个操作的都是O(1)时间的。现在增加一种新的操作MULITIPOP(S, k),该操作删除栈顶的k个元素,如果k > S.size,则删除所有元素。该操作与实际执行的POP次数呈线性关系,代码如下:

MULTIPOP(S, k)

       while not STACK-EMPTY(S) and k>0

              POP(S)

              k = k-1

 

因此,MULTIPOP的代价为min(s, k),其中s表示S.size。

 

       因为栈的大小最大为n,所以MULTIPOP的最坏情况为O(n),所以,由n个PUSH,POP,MULTIPOP组成的操作序列的最坏代价为O( n^2),因为序列可能包含O(n)个操作序列。

 

       上面的分析给出的界并不是紧确界,实际上,在一个空栈上执行nPUSH, POP, MULTIPOP的操作序列,代价最多为O(n)这是因为,当一个对象压入栈后,至多将其弹出一次。所以,对于一个非空的栈,可以执行的POP的次数(包含MULTIPOP中的POP)最多与PUSH操作次数一样,即n次。所以,对任意的n,任意一个由n个PUSH, POP, MULTIPOP组成的操作序列,最多花费O(n)。所以,每个操作的摊还代价为O(1)。

 

2:二进制计数器递增

       用一个数组A[0..k-1]表示一个k位二进制数x,x的最低位存储在A[0]中,最高位在A[k-1]中,初始情况下x=0。递增代码如下:

INCREMENT(A)

       i=0

       while i < A.length and A[i] == 1

              A[i] = 0

              i = i+1

       if i< A.length

              A[i]= 1

 

       每次INCREMENT操作的代价,与翻转的二进制位的数目呈线性关系。下图显示了将一个二进制数递增16次的情况,初始值为0,最终变为16:

 

       最坏情况下,INCREMENT执行一次需要花费Θ(k)时间,因此,初始值为0的计数器执行n个INCREMENT操作的最坏情况花费为O(nk)时间。

 

       所以,对于一个初始值为0的计数器,执行n个INCREMENT操作序列最坏情况下时间为O(n),所以,每个操作的摊还代价为O(1)。

 

二:核算法

       核算法,对不同的操作赋予不同的费用,这个费用就是摊还代价。当一个操作的摊还代价超过实际代价的时候,将差额存入数据结构中的特定对象,存入的差额称为信用。对于后续操作中,摊还代价小于实际代价的情况,信用可以用来支付差额。

       因为希望通过分析摊还代价来说明每个操作的平均代价的很小,所以应该确保n个操作序列的摊还代价是实际代价的上界。如果 表示第i个操作的真实代价,而 表示摊还代价,则对于任意的n,有:   。因为信用就是摊还代价和实际代价的差值,即   ,所以需要保持数据结构中的总信用永远为非负值。

 

1:栈操作

       栈操作的实际代价如下:PUSH   1;    POP       1;    MULTIPOP    min(k,s)。为这些操作赋予的摊还代价为:      PUSH   2;    POP       0; MULTIPOP    0。

 

       下面证明,如果按照摊还代价进行缴费,则可以支付任意的n个栈操作序列。PUSH操作时,共缴费2美元,其中1美元支付PUSH的实际代价,将剩余的1美元存入插入的元素,作为信用。这样,每个插入的元素都具有1美元的信用。1美元的信用,实际上是用来支付POP操作的预付费。当执行一个POP的时候,并不缴额外的费用,而是使用信用来支付实际代价。MULTIPOP也一样。所以,对任意的n个PUSH, POP, MULTIPOP组成的序列,总摊还代价为实际代价的上界,总摊还代价为O(n)。

 

2:二进制计数器递增

       INCREMENT的操作时间与实际翻转的位数成正比,所以可以用翻转的位数作为操作的实际代价。

       在摊还分析中,对一次置位操作(0->1),缴费2美元,用1美元支付置位操作的实际代价,另存1美元在该位,作为信用,用来支付将来的复位操作(1->0)。所以,任何时刻,计数器中任何为1的位都存有1美元的信用。对于复位操作,无须缴纳任何费用。

       所以,每个INCREMENT操作最多置位一次,因此摊还代价为2美元。所以,n个INCREMENT操作,总摊还代价为O(n)。

 

三:势能法

       势能法与核算法类似,但是势能法并不将预付代价表示为数据结构中特定对象的信用,而是表示为“势能”。势能是与整个数据结构相关联,而不是某个特定的对象。将势能释放,就可以支付未来操作的代价。

 

       势能法如下:对一个初始数据结构 执行n个操作。对于i = 1, 2,...,n, 表示第i个操作的实际代价,表示在数据结构上执行第i个操作得到的数据结构。势函数 将每个数据结构 映射到一个实数,这个值就是关联到数据结构 的势。所以,i个操作的摊还代价为每个操作的摊还代价等于其实际代价加上此操作引起的势能变化。



 

0 0
原创粉丝点击