摊还分析
摊还分析(amortized analysis):
- 主要通过求解数据结构中一系列的操作,获得操作的平均时间评价操作的代价;
- 不同于算法的期望,不涉及概率的另一种评价操作代价的方法;
主要分为三种方法:聚合分析
、核算法
、势能法
。
首先介绍两个常规例子,之后通过例子对上述方法进行说明:
例1 栈操作
提供三类栈的基本操作:
Push(x)
:将 x x x压入栈中;pop()
:删除栈顶元素;Multipop(k)
:删除栈顶 k k k个元素;
其中假设基本操作不存在对空栈的删除操作。
例2 二进制计数器
使用长度为 k k k的 01 01 01数组模拟二进制递增加法计数器,遵循规则:
- A [ 0 ] A[0] A[0]为最低位;
- 满 2 2 2进 1 1 1;
- A A A所表达的十进制数字是 ∑ i = 0 k − 1 A [ i ] ⋅ 2 i \sum_{i=0}^{k-1} A[i] \cdot 2^{i} ∑i=0k−1A[i]⋅2i;
上述两个例子中涉及的“代价”分别是最基础的操作次数,例1使用一个代价可以弹出或填入一个元素,例2则使用一个代价可以对一个 01 01 01位置进行翻转操作( 0 → 1 0\rightarrow1 0→1、 1 → 0 1\rightarrow0 1→0)。
摊还分析::聚合分析
使用聚合分析通过 n n n个操作的序列分析得到一个代价上界:
-
由栈的三个操作组成的长度为 n n n的序列,因为对空栈操作没有意义,所以在栈中的删除操作进行时只有当栈非空才有意义,所以可以执行删除(两类操作)的最多次数 = = =压入栈元素操作,综上所述理想状态下最多 2 ( n − 1 ) 2(n-1) 2(n−1)次(添加 n − 1 n-1 n−1次元素之后一次清空),这样的操作花费时间为 O ( n ) O(n) O(n),平均后每个操作的摊还代价为 O ( 1 ) O(1) O(1)。
实际上得出的是 n n n个操作的最坏代价取平均称为平均代价或摊还代价。
-
类比线性素数筛原理可以发现,在加法计数器操作 n n n次过程中每一个位置的反转是有特定时机的:
A [ 0 ] A[0] A[0]位每
1
次计算都要进行翻转,相对合理的解释是加法改变奇偶性质或者说二进制特性;A [ 1 ] A[1] A[1]位每
2
次计算都要进行翻转,解释作为二进制的特性,下图可以表达清楚;…
A [ i ] A[i] A[i]位每 2 i 2^i 2i次计算都要进行翻转
综上所述执行
n
n
n此操作翻转(代价)总次数为:
∑
i
=
0
k
−
1
⌊
n
2
i
⌋
≤
n
∑
i
=
0
∞
1
2
i
=
2
n
\begin{aligned} \sum_{i=0}^{k-1}\left\lfloor\frac{n}{2^{i}}\right\rfloor &\le n \sum_{i=0}^{\infty} \frac{1}{2^{i}} \\ &=2 n \end{aligned}
i=0∑k−1⌊2in⌋≤ni=0∑∞2i1=2n
所以最坏时间情况为
O
(
n
)
O(n)
O(n),平均(摊还)代价为
O
(
1
)
O(1)
O(1)。
摊还分析::核算法
与上述整体考虑不同,核算法将不同操作赋予“费用”并称为该操作的摊还代价,主要包含:
- 当某操作摊还代价超过实际代价时,将差额存下来;
- 当某操作摊还代价小于实际代价时,用差额去填补;
整体操作中若摊还代价是真实代价的上界,或者说结束状态差额大于等于0:
∑
i
=
1
n
c
^
i
−
∑
i
=
1
n
c
i
≥
0
\sum_{i=1}^{n} \widehat{c}_{i} - \sum_{i=1}^{n} c_{i}\geq0
i=1∑nc
i−i=1∑nci≥0
其中
c
^
i
\widehat{c}_{i}
c
i表示某操作的定义下的摊还代价,
c
i
{c}_{i}
ci表示某操作的真实代价,那么上界存在,由于摊还分析想得到的就是证明一系列操作的时间复杂度很小,所以只要证明了合理的上界那么就完成了摊还分析的目的。
其中上述的差额在操作过程中允许为负数,但为了证明上界所以在系列操作结束后必须为非负数。
-
首先分析栈操作的实际代价:
①
push(x)
:1②
pop()
:1③
multipop(k)
:栈中元素与 k k k最小值栈操作的摊还代价:
①
push(x)
:2②
pop()
:0③
multipop(k)
:0综合理解为每个
push(x)
多存1个单位的费用,删除的时候不管删多少没有push
的元素多,那么就可以用前面存的钱垫付后面的操作费用,最终差额一定大于等于 0 0 0,所以总摊还代价依旧是 O ( n ) O(n) O(n)。 -
二进制的翻转实际代价都为 1 1 1,之后定义摊还代价为:
① 0 → 1 0\rightarrow1 0→1:2
② 1 → 0 1\rightarrow0 1→0:0
与上述理解相同,在进行变 1 1 1翻转时使用一个实际代价存入一个单位的费用差额,之后复位时用存的钱垫付,因为所有的复位都源于翻转但同时所有复位的次数一定小于等于翻转,所以差额任何时刻都是非负的,总摊还代价为 O ( n ) O(n) O(n)。
摊还分析::势能法
势能法针对的不同于核算法,其更加注重对整个操作序列过程的分析:
-
将序列中每个操作 D i D_i Di定义为函数 Φ ( D i ) \Phi(D_i) Φ(Di)表示操作进行到当前状态时的“势”;
-
摊还代价定义为:
c ^ i = c i + Φ ( D i ) − Φ ( D i − 1 ) \widehat{c}_{i}=c_{i}+\Phi\left(D_{i}\right)-\Phi\left(D_{i-1}\right) c i=ci+Φ(Di)−Φ(Di−1)
势函数的差可以理解为当前操作的额外代价,加上真实代价后为摊还代价; -
对式子进行求和操作时可以化简为:
∑ i = 1 n c ^ i = ∑ i = 1 n c i + Φ ( D n ) − Φ ( D 0 ) \sum_{i=1}^{n} \widehat{c}_{i}=\sum_{i=1}^{n} c_{i}+\Phi\left(D_{n}\right)-\Phi\left(D_{0}\right) i=1∑nc i=i=1∑nci+Φ(Dn)−Φ(D0)
通常将 Φ ( D 0 ) \Phi(D_{0}) Φ(D0)简记为 0 0 0,所以如果要完成前面几个方法的证明则需要找到一个势函数最终操作结束时为非负的使得摊还分析复杂度为实际代价的上界。
现针对两个例子进行说明:
-
势函数定义栈操作中栈内的元素数量为其势函数,初始状态势函数 Φ ( D 0 ) = 0 \Phi(D_{0})=0 Φ(D0)=0之后三种基础操作对应的势函数变化为:
push(x)
: Φ ( D i ) − Φ ( D i − 1 ) = 1 \Phi\left(D_{i}\right)-\Phi\left(D_{i-1}\right)=1 Φ(Di)−Φ(Di−1)=1;multipop(k)
: Φ ( D i ) − Φ ( D i − 1 ) = − k \Phi\left(D_{i}\right)-\Phi\left(D_{i-1}\right)=-k Φ(Di)−Φ(Di−1)=−k;删除栈顶元素类比 k = 1 k=1 k=1;
那么入栈操作的摊还代价为:
c ^ i = 1 + 1 = 2 \widehat{c}_{i}=1+1=2 c i=1+1=2
删除栈中元素摊还代价为:
c ^ i = k + ( − k ) = 0 \widehat{c}_{i}=k+(-k)=0 c i=k+(−k)=0
那么总的摊还代价依旧为真实代价上界,且总摊还代价为 O ( n ) O(n) O(n)。 -
在加法计数器中定义:
势函数 b i b_i bi表示当前操作后计数器中 1 1 1的个数;
复位 t i t_i ti表示当前操作中复位( 1 → 0 1\rightarrow0 1→0)的个数;
上述形况下除溢出 k k k位加法器以外,过程中都会有翻转( 0 → 1 0\rightarrow1 0→1)情况:
1.当计数器中 k k k位全为 1 1 1(溢出情况),那么继续加法后满足 b i − 1 = t i = k b_{i-1}=t_i=k bi−1=ti=k;
2.当正常情况下满足 b i = b i − 1 − t i + 1 b_i=b_{i-1}-t_i+1 bi=bi−1−ti+1;
正常情况下解释为上一阶段的 1 1 1的个数减去复位个数加上翻转个数,所以综上所述势函数满足:
Φ ( D i ) − Φ ( D i − 1 ) ≤ ( b i − 1 − t i + 1 ) − b i − 1 = 1 − t i \begin{aligned} \Phi\left(D_{i}\right)-\Phi\left(D_{i-1}\right)\le&(b_{i-1}-t_i+1)-b_{i-1}\\ =&1-t_i \end{aligned} Φ(Di)−Φ(Di−1)≤=(bi−1−ti+1)−bi−11−ti
同样情况下的真实代价位复位数量 t i t_i ti+翻转数量( 1 1 1),所以摊还代价位:
c i ≤ ( 1 + t i ) + ( 1 − t i ) = 2 c_i\le (1+t_i)+(1-t_i)=2 ci≤(1+ti)+(1−ti)=2
那么最终取得求和后的摊还分析位:
∑ i = 1 n c i ≤ ∑ i = 1 n 2 − b n + b 0 = 2 n − b n + b 0 \begin{aligned} \sum_{i=1}^{n} c_{i} & \leq \sum_{i=1}^{n} 2-b_{n}+b_{0} \\ &=2 n-b_{n}+b_{0} \end{aligned} i=1∑nci≤i=1∑n2−bn+b0=2n−bn+b0
之后取得势能法计算下的上界总摊还代价复杂度为 O ( n ) O(n) O(n)。