算法导论(第三版) 17.1-17.3
1.什么是平摊分析,平摊分析原理?
平摊分析目的是分析给定 数据结构上的n个操作代价上界
对一个数据结构 执行一个操作序列:
- 有的代价很高
- 有的代价一般
- 有的代价很低
如果我们想求各个操作的平均代价? 整个序列的代价是多少?
那我们就要将总的代价平摊到 每个操作上(不涉及概率 异于平均分析
),这就是平摊代价
在平摊分析中,执行一系列数据结构操作所需要时间是通过对执行的所有操作求平均而得出的
2.平摊分析三种方法
- 聚集方法(每个操作的代价)
- 确定n个操作的上界T(n), 每个操作平摊T(n)/n
- 会计方法(整个操作序列的代价)
- 不同类型操作赋予不同的平摊代价
- 某些操作在数据结构的特殊对象上“预付”代价
- 势能方法(整个操作序列的代价)
- 不同类型操作赋予不同的平摊代价
- “预付”的代价作为整个数据结构的“能量”
3.聚集方法
聚集方法目的是分析n个操作系列中 每个操作的复杂性上界
实例 栈操作:
普通栈操作
- PUSH(S,x):将对象压入栈S
- POP(S):弹出并返回S的顶端元素
时间代价:
- 两个操作的运行时间都是0(1)
- 我们可把每个操作的代价视为1
- n个PUSH和POP操作系列的总代价是n
- 显然n个PUSH和POP操作系列的总代价是n
现在我们定义新的栈操作
- 操作MULTIPOP(S,k):
- 去掉S的k个顶端对象
- 或当S中包含少于k个对象时弹出整个栈
其实际运行时间与实际执行的POP操作数成线性关系
展开分析:
n个栈操作序列由Push、Pop和Multipop组成
粗略分析
- 最坏情况下,每个操作都是Multipop
- 每个Multipop的代价最坏是
- 操作系列的最坏代价为=
- 平摊代价为
但是这么分析太粗糙了。因为一个对象在每次被压入栈后至多被弹出一次。
精细分析
- 所以在一个非空栈上调用POP的次数(包括在MULTIPOP内的调用)至多等于PUSH的次数,即至多为n。
- 在非空栈上调用Pop的次数(包括在Multipop内的调用) 至多为Push执行的次数, 即至多为n
- 因此从直观感觉上看
- 于是:最坏情况下这样的一个操作序列的时间复杂度最多为
- 平摊代价
4.会计方法
什么是会计方法?
- 一个操作序列中有不同类型的操作
- 不同类型的操作的操作代价各不相同
- 于是我们为每种操作分配不同的平摊代价
- 平摊代价可能比实际代价大,也可能比实际代价小
操作被执行时,支付了平摊代价
- 只要我们能保证:在任何操作序列上,存款的总额非负 就能够知道平摊代价的总和不小于实际代价的总和
则平摊代价的总和就算实际代价的上界
实例 还是栈操作:
1.各栈操作的实际代价:
| 操作 | 代价 |
|---|---|
| PUSH | 1 |
| POP | 1 |
| MULTIPOP | min(k,s) |
2.各栈操作的平摊代价:
| 操作 | 代价 |
|---|---|
| PUSH | 2 |
| POP | 0 |
| MULTIPOP | 0 |
这是我们主观来定义的,此时平坦代价足以支付实际代价
每次一位被置1时,付2美元
- 1美元用于置1的开销
- 1美元存储在该“1”位上,用于支付其被置0时的开销
- 置0操作无需再付款
- Cost(Increment)=2
长度为n的操作序列中:PUSH操作的个数≤n
于是:平摊代价的总和≤2n
所以 n个栈操作序列的总平摊代价 –
5.势能方法(最常用的平摊分析方法)
在会计方法中,如果操作的平摊代价比实际代价大我们将余额与具体的数据对象关联
如果我们将这些余额都与整个数据结构关联,所有的这样的余额之和,构成——数据结构的势能
- 如果操作的平摊代价大于操作的实际代价-势能增加
- 如果操作的平摊代价小于操作的实际代价,要用数据结构的势能来支付实际代价势能减少
5.1 数据结构势能的定义
考虑在初始数据结构上执行n个操作
对于操作i :
- 操作i的实际代价为
- 操作i将数据结构 变为
- 数据结构 的势能是一个实数 , 是一个正函数
- 操作i的平摊代价:
实例 还是栈操作:
作用于包含s个对象的栈上的栈操作的平摊代价
平摊分析:
每个栈操作的平摊代价都是0(1)
n个操作序列的总平摊代价就是O(n)
因为,
n个操作的总平摊代价即为总的实际代价的一个上界,即n个操作的最坏情况代价为O(n)
6.实例:动态表性能平摊分析
6.1什么是动态表
动态表支持的操作:
- TABLE-INSERT:将某一元素插入表中
- TABLE-DELETE:将一个元素从表中删除
数据结构:用一个(一组)数组来实现动态表
非空表T的装载因子 存储的对象数/表大小
设T表示一个动态表:
- table[T]是一个指向表示表的存储块的指针
- num[T]包含了表中的项数
- size[T]是T的大小
- 开始时,num[T]=size[T]=0
6.2当表装满时?
插入一个数组元素时,完成的操作包括:
- 分配一个包含比原表更多的槽的新表
- 再将原表中的各项复制到新表中去
常用的启发式技术是分配一个比原表大一倍的新表
- 只对表执行插入操作,则表的装载因子总是至少为1/2
- 浪费掉的空间就始终不会超过表总空间的一半
6.3 聚集分析
粗略分析
考察第i次操作的代价Ci
- 如果i=1, =1;
- 如果num[T]<size[T], =1;
- 如果num[T]=size[T],=i;
共有n次操作
- 最坏情况下,每次进行n次操作,总的代价上界为n2
但是,这个界不精确
- n次TABLE—INSERT操作并不常常包括扩张表的代价
- 仅当i-1为2的整数幂时第i次操作才会引起一次表的扩张
。
聚集分析-精细分析
第i次操作的代价Ci •
- 如果i=2m,=i;否则=1
- n 次TABLE—INSERT操作的总代价
-
因此每一操作的平摊代价为3n/n=3
6.4 会计方法
每次执行TABLE—INSERT平摊代价为3
- 1支付第10步中的基本插入操作的实际代价
- 1作为自身的存款
- 1存入表中第一个没有存款的数据上
当发生表的扩张时,数据的复制的代价由数据上的存款 来支付 。
任何时候,存款总和非负
初始为空的表上n次TABLE-INSERT操作的平摊代价总和为3n
6.5 势能方法
如果势能函数满足
- 刚扩充完, φ (T)=0 势能最低
- 表满时 φ (T)=size(T) 势能高
于是我们定义定义
- 由于 ,故
- 因此,n次TABLE-INSERT操作的总的平摊代价是总的实际代价 的一个上界
第i次操作的平摊代价
- 如果未发生扩张, =3
- 否则如果发生扩张, =3
所以初始为空的表上n次插入操作的代价的上界为3n
6.6 对于表的收缩,同理
此时更适合用势能法进行分析
7.习题
3.1. 请比较平摊分析中聚集法和势能法的优劣
3.2. 请讨论平摊分析中会计法和势能法的区别
3.3. 请讨论平摊分析和概率分析的区别
3.4 对某个数据结构执行大小为 n 的一个操作序列,若 i 为 2 的整数幂,则第 i 个操作的代价 为 i,否则为 1。请利用会计方法分析每次操作的平摊代价
3.5 Bill 提出了一种叫做翻转堆栈的数据结构,翻转堆栈只支持 Flipping-Push() 函数。在每 次 Flipping-Push() 中,首先压栈,并检查堆栈中的对象数目数是否是 2 的幂(2^i )。如果 是,则堆栈中的所有对象将翻转。例如我们使用 Flipping-Push() 将对象 1,2,3,4 压入堆栈 中,堆栈中的内容(从底向上看)在每次压栈后为:
(1) ⇒ (2, 1) ⇒ (2, 1, 3) ⇒ (4, 3, 1, 2) Bill
请求你分别使用聚集分析、会计方法、势能方法分析 Flipping-Push() 函数的平摊代价 (amortized cost), 堆栈反转的代价等于堆栈现有对象数目
3.6 有两个堆栈 A 和 B,都可以使用以下 5 种操作(A 大小为 n,B 大小为 m):
PushA(x):x 压入堆栈 A,实际代价 =1 PushB(x):x 压入堆栈 B,实际代价 =1
MultiPopA(k): 从 A 中弹出 min{k, n} 个元素,实际代价 =min{k, n}
MultiPopB(k): 从 B 中弹出 min{k, m} 个元素,实际代价 =min{k, m}
Transfer(k): 从 A 中弹出元素并压入 B 中,直到转移 k 个元素或 A 为空,实际代价 = 转 移元素数目 (a)MultiPopA(k),MultiPopB(k),Transfer(k) 三种操作最坏情况下的时间复杂度为多少?
(b) 定义一个势能函数 Φ(n, m), 用它证明以上操作平摊代价为 O(1)
- 思考题
平摊分析还可能有什么方法?