摊还分析¶
Abstract
- Amortized 摊还分析:求操作序列中所有操作的平均时间,评价操作的代价。用于特定操作非常慢,但一般操作很快的算法。
摊还分析产生一个平均代价的上界,比最坏情况估计要好。
摊还分析不使用概率知识。
聚合分析¶
聚合分析就是求 \(n\) 个操作序列的总代价上限 \(T(n)\),然后得到平均代价 \(T(n)/n\)。
聚合分析的例子有:
MultiPop(S, k)
:从栈顶弹出 \(k\) 个元素- 如果对这个操作使用最坏情况分析,会得到 \(O(n^2)\) 的复杂度。
- 但我们知道栈中的元素最多会被弹出一次,所以对 \(n\) 个操作的总代价是 \(O(n)\),平均代价是 \(O(1)\)。
- 二进制计数器递增
INCREMENT
:每次递增计数器- 如果进行最坏情况分析,最坏情况可能需要反转所有的位,得到复杂度 \(O(nk)\)。
- 但是我们知道,每次递增计数器,只有最低位需要反转,下一位每两次递增需要反转一次,以此类推。对等比数列进行求和可以得到总代价是 \(O(n)\),从而平均代价是 \(O(1)\)。
很朴素的一种分析方法。
核算法¶
核算法计算每个操作的代价和信用,用前面操作的信用来支付后面操作的代价。核算法要求摊还代价满足:\(\sum \hat{c}_i \geq \sum c_i\)。
例子:
MultiPop
:- Push 操作:实际代价 1,产生信用 1
- Pop 操作:实际代价 1,消耗信用 1
- MultiPop 操作:实际代价 \(k\),消耗信用 \(k\)
- 将信用和代价合并,得到 Push 操作摊还代价是 2,其余两个操作摊还代价是 0。
- 双栈实现队列:
- 入队操作:实际代价 1,产生信用 2。摊还代价 3。
- 出队操作:总是产生信用 2
- 如果栈 2 不为空,直接出栈,实际代价 1。摊还代价 3。
- 如果栈 2 为空,将栈 1 全部出栈到栈 2,实际代价 \(2n + 1\)。每次移动元素消耗 2 个信用,共消耗 \(2n\) 个信用。摊还代价 \(2n + 1 - 2n + 2 = 3\)。
- 综上,所有操作的摊还代价都是 3。
势能法¶
势能法将特定结构与势能关联,操作引起势能的变化。我们通过势能变化来计算摊还代价:\(\sum \hat{c}_i = \sum c_i + \sum \Phi_i - \Phi_{\text{init}}\)。
例子:
MultiPop
中,使用栈的大小作为势能。
动态数组的扩张
动态数组插入的最坏情况是 \(O(n)\)(创建新数组并拷贝 \(n\) 个元素),简单分析得出插入操作上界为 \(n*O(n)=O(n^2)\)。然而并不是每次插入都是 \(O(n)\),这个上界并不好。
采用摊还分析,计算如下:
\[
\frac{(1+1+1+\dots)+(1+2+4+\dots)}{n}=\frac{n+\lfloor\log_2(n-1)\rfloor+1}{n}\leq3
\]
得到插入的平均时间复杂度为 \(O(1)\)。