#算法 平摊分析——一种特殊的分析方法

本文深入探讨了平摊分析的基本概念,介绍了聚集方法、会计方法和势能方法三种平摊分析方法,通过具体实例如栈操作和平摊分析动态表性能,详细解析了各种方法的应用和优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法导论(第三版) 17.1-17.3

1.什么是平摊分析,平摊分析原理?

平摊分析目的是分析给定 数据结构上的n个操作代价上界
对一个数据结构 执行一个操作序列:

  • 有的代价很高
  • 有的代价一般
  • 有的代价很低

如果我们想求各个操作的平均代价? 整个序列的代价是多少?
那我们就要将总的代价平摊到 每个操作上(不涉及概率 异于平均分析
),这就是平摊代价

在平摊分析中,执行一系列数据结构操作所需要时间是通过对执行的所有操作求平均而得出的

2.平摊分析三种方法

  1. 聚集方法(每个操作的代价)
  • 确定n个操作的上界T(n), 每个操作平摊T(n)/n
  1. 会计方法(整个操作序列的代价)
  • 不同类型操作赋予不同的平摊代价
  • 某些操作在数据结构的特殊对象上“预付”代价
  1. 势能方法(整个操作序列的代价)
  • 不同类型操作赋予不同的平摊代价
  • “预付”的代价作为整个数据结构的“能量”

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的代价最坏是O(n)O(n)O(n)
  • 操作系列的最坏代价为T(n)T(n)T(n)O(n2)O(n^2)O(n2)
  • 平摊代价为T(n)/n=O(n)T(n)/n = O(n)T(n)/nO(n)

但是这么分析太粗糙了。因为一个对象在每次被压入栈后至多被弹出一次。
精细分析

  • 所以在一个非空栈上调用POP的次数(包括在MULTIPOP内的调用)至多等于PUSH的次数,即至多为n。
  • 在非空栈上调用Pop的次数(包括在Multipop内的调用) 至多为Push执行的次数, 即至多为n
  • 因此从直观感觉上看T(n)≤2nT(n)≤2nT(n)2n
  • 于是:最坏情况下这样的一个操作序列的时间复杂度最多为O(n)O(n)O(n)
  • 平摊代价=T(n)/n=O(1)=T(n)/n=O(1)=T(n)/n=O(1)

4.会计方法

什么是会计方法?

  • 一个操作序列中有不同类型的操作
  • 不同类型的操作的操作代价各不相同
  • 于是我们为每种操作分配不同的平摊代价
  • 平摊代价可能比实际代价大,也可能比实际代价小

操作被执行时,支付了平摊代价

  • 只要我们能保证:在任何操作序列上,存款的总额非负 就能够知道平摊代价的总和不小于实际代价的总和
    则平摊代价的总和就算实际代价的上界
实例 还是栈操作:

1.各栈操作的实际代价:

操作代价
PUSH1
POP1
MULTIPOPmin(k,s)

2.各栈操作的平摊代价:

操作代价
PUSH2
POP0
MULTIPOP0

这是我们主观来定义的,此时平摊代价足以支付实际代价(这是我们需要保证的)

每次一位被置1时,付2喵喵币

  • 1喵喵币用于置1的开销
  • 另外1喵喵币当作余额存储在该“1”位上,用于支付其被置0时的开销
  • 置0操作无需再付款
  • Cost(Increment)=2

长度为n的操作序列中:PUSH操作的个数≤n
于是:平摊代价的总和≤2n

所以 n个栈操作序列的总平摊代价 – O(n)O(n)O(n)

5.势能方法(最常用的平摊分析方法)

在会计方法中,如果操作的平摊代价比实际代价大我们将余额与具体的数据对象关联

如果我们将这些余额都与整个数据结构关联,所有的这样的余额之和,构成——数据结构的势能

  • 如果操作的平摊代价大于操作的实际代价-势能增加
  • 如果操作的平摊代价小于操作的实际代价,要用数据结构的势能来支付实际代价势能减少
5.1 数据结构势能的定义

考虑在初始数据结构D0D_0D0上执行n个操作
对于操作i :

  • 操作i的实际代价为cic_ici
  • 操作i将数据结构Di−1D_ {i-1}Di1 变为DiD_iDi
  • 数据结构DiD_iDi 的势能是一个实数 φ(Di)φ (D_i)φ(Di), 是一个正函数
  • 操作i的平摊代价: ai=ci+φ(Di)−φ(Di−1)a_i=c_i + φ (D_i)-φ (D_ {i-1})aici+φ(Di)φ(Di1)
    在这里插入图片描述
    在这里插入图片描述
实例 还是栈操作:

在这里插入图片描述
作用于包含s个对象的栈上的栈操作的平摊代价

平摊分析:
每个栈操作的平摊代价都是0(1)
n个操作序列的总平摊代价就是O(n)
因为φ(Di)≥φ(D0)φ (D_i)≥φ (D_0)φ(Di)φ(D0)
n个操作的总平摊代价即为总的实际代价的一个上界,即n个操作的最坏情况代价为O(n)

6.实例:动态表性能平摊分析

6.1什么是动态表

动态表支持的操作:

  • TABLE-INSERT:将某一元素插入表中
  • TABLE-DELETE:将一个元素从表中删除

数据结构:用一个(一组)数组来实现动态表

非空表T的装载因子 α(T)=Tα (T)= Tα(T)=T存储的对象数/表大小
设T表示一个动态表:

  • table[T]是一个指向表示表的存储块的指针
  • num[T]包含了表中的项数
  • size[T]是T的大小
  • 开始时,num[T]=size[T]=0
6.2当表装满时?

插入一个数组元素时,完成的操作包括:

  1. 分配一个包含比原表更多的槽的新表
  2. 再将原表中的各项复制到新表中去

常用的启发式技术是分配一个比原表大一倍的新表

  • 只对表执行插入操作,则表的装载因子总是至少为1/2
  • 浪费掉的空间就始终不会超过表总空间的一半

在这里插入图片描述

6.3 聚集分析

粗略分析
考察第i次操作的代价Ci

  • 如果i=1, CiC_iCi=1;
  • 如果num[T]<size[T], CiC_iCi=1;
  • 如果num[T]=size[T],CiC_iCi=i;

共有n次操作

  • 最坏情况下,每次进行n次操作,总的代价上界为n2

但是,这个界不精确

  1. n次TABLE—INSERT操作并不常常包括扩张表的代价
  2. 仅当i-1为2的整数幂时第i次操作才会引起一次表的扩张


聚集分析-精细分析
第i次操作的代价Ci •

  • 如果i=2m,CiC_iCi=i;否则CiC_iCi=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) 势能高

于是我们定义定义 φ(T)=2∗num[T]−size[T]φ (T)=2*num[T]-size[T]φ(T)=2num[T]size[T]

  • 由于 num[T]≥size[T]/2num[T]≥size[T]/2num[T]size[T]/2,故 φ(T)≥0φ(T)≥0φ(T)0
  • 因此,n次TABLE-INSERT操作的总的平摊代价是总的实际代价 的一个上界

第i次操作的平摊代价

  • 如果未发生扩张, αiα_iαi=3
  • 否则如果发生扩张, αiα_iαi=3
    所以初始为空的表上n次插入操作的代价的上界为3n
6.6 对于表的收缩,同理

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
此时更适合用势能法进行分析
在这里插入图片描述
在这里插入图片描述

7.习题

3.1. 请比较平摊分析中聚集法和势能法的优劣

聚集法较为简单,但是对处理的问题有很大的要求,因为是对每一步操作进行估量,因此要求必须从开始头进行分析,而且是连续的,并且其估计结果较为粗略。

势能法 处理的情况相较于聚集法更多,没有那么多的限制

3.2. 请讨论平摊分析中会计法和势能法的区别
会计法中,将分析的余额与每个具体的数据的操作对象关联,要求操作的平摊代价必须比实际代价大
势能法中则是将这些余额与整个数据结构关联,所有的这样的余额之和,构成——数据结构的势能;如果操作的平摊代价大于操作的实际代价-势能增加;如果操作的平摊代价小于操作的实际代价,要用数据结构的势能来支付实际代价-势能减少

3.3. 请讨论平摊分析和概率分析的区别
平摊分析将总的代价平摊到每个操作上,分析过程不涉及任何概率,这异于平均分析

3.4 对某个数据结构执行大小为 n 的一个操作序列,若 i 为 2 的整数幂,则第 i 个操作的代价 为 i,否则为 1。请利用会计方法分析每次操作的平摊代价

这个题类似动态表的平摊分析,基本上都可以套用
会计方法:每次操作的代价为3,
其中1用于支付本身的代价,
1用于作为自身的存款,
1用于存入数据结构中第一个没有存款的数据
比如我们举个例子
在这里插入图片描述
对于这样的一个数据结构,显然每次出现 2 的整数幂,就会消耗之前存放的所有数据上的存款,比如由2i2^i2i个操作执行到到2i+12^{i+1}2i+1个操作时,就会消耗0到2i2^i2i2i2^i2i2i+12^{i+1}2i+1的总存款,再执行到2i+22^{i+2}2i+2的时候,前面的存款必须补充回去,因此我们还需要1用于用于存入数据结构中第一个没有存款的数据

所以总的平摊操作代价之和 即为3n,每次平摊代价为3

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), 堆栈反转的代价等于堆栈现有对象数目

这个和前面的动态表的异曲同工,都是每次遇到2 的幂(2i2^i2i )其代价为数字本身,其他则为1,
在这里插入图片描述

聚集方法:
粗略分析: 每个操作的最坏代价为nnn,则总复杂度为O(n2)O(n^2)O(n2)
精细分析:对于n个操作,则会有lgnlgnlgn级别的操作代价为其本身,其他的代价为1
则总代价为
在这里插入图片描述
平均到每个操作上代价还是3

会计方法:
每次操作的代价为3,
其中1用于支付本身的代价,
1用于作为自身的存款,
1用于存入数据结构中第一个没有存款的数据

势能方法
每当出现一个2 的幂(2i2^i2i )的时候,消耗所有的势能作为操作的代价
在遇到2的幂之前,势能最高
定义势能函数φ(x)=2∗x−2⌊log2x⌋+1φ (x)=2*x- 2^{\lfloor log_{2}x \rfloor+1}φ(x)=2x2log2x+1
不难发现φ(x)=2∗x−2⌊log2x⌋+1≥0恒成立φ (x)=2*x- 2^{\lfloor log_{2}x \rfloor+1} \geq0恒成立φ(x)=2x2log2x+10
满足我们的要求
因此,n次操作的总的平摊代价是总的实际代价 的一个上界

所以平摊代价为3n

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)

(a):
最坏代价都为O(n)O(n)O(n)级别的
(b):
势能函数 Φ(n, m)= 3n+m
4. 思考题
平摊分析还可能有什么方法?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值