算法导论之平摊分析(动态表)

平摊分析,amortizedanalysis,对数据结构执行的所有操作的总和时间是油由求平均而得出,用来证明一系列操作中,通过对所有操作求平均代价,即时某一操作具有较大代价,但平均代价还是小的。导论中这个和平均情况分析不同,我自己到没感觉出不同,同样是求平均。Amortized,摊销之意,就是将把总代价平均到一个周期内来承担。

平摊分析有什么意义呢?主要为认识数据结构。对数据结构的一系列操作,数据结构并非总是静态的,每一个操作都可能引发数据结构的改变,而影响下一步操作的执行代价,用总体来刻画,可以为我们选择怎样的数据结构以及如何对数据结构执行操作提供性能上的量化判断。

平摊分析有三种方法:聚集分析、记账法、势能法。

聚集分析,证明对所有的n个操作所构成的序列的总时间在最坏情况下为T(n)。在最坏情况下,每个操作的平均代价就是T(n)/n。总体最坏,平均下去就是每个操作的最坏情况。

记账法,则是通过对操作赋予价值,价值储蓄用于后续支出。如果一个操作的代价小于平摊代价,就是时间花费小,那么可以储蓄起来,用于给那些代价大于平摊代价的操作。只要确保总储蓄最后大于等于零,那么平摊就成立。

势能方法,思路和记账法一样,对数据结构的每一个操作用能或势来表示,也就是先存起能量,支付后面需要消耗能量的操作。需要定义一个势函数。

通过二进制计数器累进来说明这三类方法。K位二进制计数器,用A[0..k-1]表示,位数长度为k,最低位在A[0],最高位在A[k-1]中。计数器加1的函数如下:

Fun_Increment(A){

    i=0;

    while i

        do A[i]=0;//该位是1,加1就累进一位

           i=i+1;

    if i

        then A[i]=1;

}

分析该函数最坏情况下的执行次数,数组中位数全是1,那么就要执行循环位数长度k次,那么要执行n次加1操作,就要O(nk)代价,这是最坏情况下。但这个大致分析,明眼就能看出虽然正确,但不够精确。深入下,把0和1的累进看做翻牌,实际上每一位都是前一位的1/2翻牌,就是隔一次才会翻牌。用初始位0来说明,n次操作,A[0]要翻牌n次,但A[1]只要翻牌1/2次,同样依次往高位去,都是呈1/2来翻牌的次数。可得:


如此,总代价是O(n),平摊代价就是O(1)。

利用平摊分析,证明表的插入和删除操作平摊代价为O(1),以研究表的动态扩展和压缩。用表的装载因子作为表压缩和扩张的标志,装载因子是表中存储元素个数除以表大小的结果。当装载因子等于1时,表满,则扩展原表的两倍,使新表的装载因子为1/2;当装载因子小于1/2时,压缩原表一半。表插入和删除的操作次数主要是插入和删除本身1次动作,以及如果装载因子超出界限,插入原表到新表的操作。动态表的扩展和压缩可以按照以上方法求得平摊分析结果。动态表的函数和平摊分析就不多叙述,在算法思想上并无可书写之处,倒是平摊分析的意义要在最后再理解一次。

平摊分析作为算法性能分析的方法,其目的在平均操作的代价来评估算法的应用价值。从整体上考虑其性能代价,忽略部分操作的高消耗,从而得出更精确的算法开销。针对数据结构的每一次操作都带来结构的变化,换句话说,这一系列对数据结构的操作不是独立,是互相关联,这样每次操作的代价不应该由独立的该次操作来承担开销,而应该是所有操作来承担。从操作的因果关系来说,以二进制计数器为例,前一次操作的复位个数决定后一次操作的复位个数,这样的关系就是平摊分析的价值所在。因为这个操作序列中各个操作可能会是相互制约的,所以开销很大的那些个个操作,在操作序列总开销中的贡献也应该被平衡和分担。

平摊分析的输入是问题的具体条件以及若干个相互关联的操作序列,输出是在该问题的条件下,这个动作序列的每一个动作(不管动作的执行内容)的平摊性能开销并不等于最大开销动作的最坏时间,而是平均开销代价。如果我们设计了一个数据结构,然后施以一系列操作,通过总体平摊开销来反映其性能,比用某一个具体操作的最坏代价来考量更有意义。可以通俗地说:平均身高更能反映一个地区的水平,而不是通过最高和最低部分。

你可能感兴趣的:(Algorithm,算法导论专栏)