摊还分析
——求
数据结构
的
一个操作序列
中所执行的所有操作的平均时间,来评价操作的代价
聚合分析:确定一个n个操作的序列的总代价的上界T(n)【即最坏情况下花费的总时间】,从而每个操作的平均代价为T(n)/n;所有操作具有相同的摊还代价
覈算法:分析每个操作的摊还代价;同时将序列中某些较早的操作的“余额”作为“预付信用”保存起来,
与数据结构中的特定对象相关联;在序列中随后的部分,保存的信用可以用来为那些缴费少于实际代价的操作支付差额
势能法:与覈算法类似,不同的是其信用是作为一个整体保存起来的,而不是将信用与单个对象关联分开保存
聚合分析 1. 带有额外操作MULTIPOP操作的栈 先简单分析一个界,任意一个栈操作的最坏情况时间为O(n)【即一个MULTIPOP操作的最坏情况代价,栈的大小为n】,n个操作,因此最坏情况代价为O(n^2) 但上面的分析得到的不是一个确界 实际上,虽然一个MULTIPOP操作可能代价很高,但在一个空栈上执行n个PUSH/POP/MULTIPOP的操作序列,代价至多是O(n);因为
将一个对象压入栈后,至多将其弹出一次 从而一个操作的平均时间为O(n)/n = O(1) 2. 二进制计数器递增 一个k位二进制计数器,表示为数组A[0..k-1],初值为0;为了将1(模2^k)加到计数器的值上,使用过程如下 INCREMENT(A) i = 0 while i < A.length && A[i] == 1
//遇到1,进位 A[i] = 0 i = i + 1 if i < A.length
//如果一开始所有位都为1,则A变为0 A[i] = 1 先简单分析一个界,最坏情况下INCREMENT操作执行一次花费O(k)时间,即所有位都为1,一共有n个操作,因此最坏情况时间O(nk) 同样的上面分析得到的不是一个确界 分析可得,对A中每一位,n个INCREMENT操作组成的序列,A[i]会翻转 n / 2^i次,从而总翻转操作为O(n) 从而
一个操作的平均时间为O(n)/n = O(1)
覈算法【关键在于赋予什么操作什么摊还代价】 对不同操作赋予不同费用,称为它的摊还代价,当一个操作的摊还代价超出实际代价时,
将差额存入数据结构中的特定对象,存入的差额称为信用。从而,一个操作的摊还代价分解为实际代价和信用 必须保证总摊还代价大于等于总实际代价 1.
带有额外操作MULTIPOP操作的栈
赋予PUSH、POP、MULTIPOP三种操作2/0/0的摊还代价
一个PUSH操作用1支付压栈的实际代价,将剩余的1作为信用,用于支付其出栈;POP、MULTIPOP不需要支付任何费用
因此总摊还代价为O(n),总实际代价也是。
2. 二进制计数器递增
对一次置为操作,赋予其2的摊还代价,1用于支付置位的实际代价,将剩余的1作为信用,用于支付复位
而在一次INCREMENT 中,顶多置位一次,即伪代码中最后一句,A[i] = 1,因此总的摊还代价为O(n)【n个操作,顶多位置n次】
从而也为总实际代价
势能法【关键在于势能函数的定义】
对初始数据结构D0执行n个操作;Di为在Di-1上执行第i个操作得到的结果数据结构,势能函数s(Di)为关联到数据结构Di的势
第i个操作的摊还代价ci’ = ci + s(Di) – s(Di-1) 即等于实际代价加上此操作引起的势能变化 总摊还代价为C’ = C + s(Dn) – s(D0);因此如果能定义一个势函数,是的s(Dn) >= s(D0),则总摊还代价为总实际代价的一个上界
1.
带有额外操作MULTIPOP操作的栈
定义势能函数为栈中的对象数量
;由于栈中对象数目不可能为负,因此s(Di) >= 0
对一个PUSH操作:ci’ = ci’ + s(Di) – s(Di-1) = 1+ 1 = 2
对一个POP操作:ci’ = c’ + s(Di) – s(Di-1) = 1 – 1= 0
对MULTIPOP也一样
每一个操作,摊还代价都是O(1),n个操作摊还代价为O(n)
2. 二进制计数器递增
定义势能函数为bi——i次操作后计数器中1的个数,从而总是大于等于0
分析:假设第i个操作将ti个位复位,则其实际代价最多为ti + 1
如果bi = 0,则第i个操作将所有位都复位了,从而bi-1 = ti =k
如果bi >0,则bi = bi-1 – ti + 1
从而bi <= bi-1 – ti + 1
从而s(Di) – s(Di-1) <= bi-1 – ti + 1 – bi-1 = 1-ti
摊还代价ci’ = ci + s(Di) – s(Di-1) <= (ti + 1) + (1 – ti) = 2
从而总的摊还代价为O(n)