均摊分析
动态数组倍增扩容单次 ,为什么说尾部追加是 ?哈希表扩容 rehash 全表,为什么插入仍是 ?答案是均摊分析:不看单次操作的最坏代价,而看一串操作的总代价。
聚合分析(aggregate)
Section titled “聚合分析(aggregate)”直接算 次操作的总代价,再除以 。
动态数组的例子:从容量 1 开始,追加 次。扩容发生在大小达到 时,拷贝总代价为
即 次追加总代价 ,每次均摊 。倍增因子的作用在这里看得清楚:2 倍意味着总拷贝不超过 ,1.5 倍意味着不超过 但空间浪费更少。
记账法(accounting)
Section titled “记账法(accounting)”给每个操作多收"费",存进"银行"支付未来的昂贵操作。动态数组每次追加收 3 个单位:1 个付自己,2 个存着——扩容时每个老元素恰好有 2 个单位付拷贝。只要银行余额永不为负,均摊界就成立。
势能法(potential)
Section titled “势能法(potential)”定义势函数 (系统的"预付能量"),第 次操作的均摊代价为
即实际代价加上势能变化。若 且 ,则总均摊代价是总实际代价的上界。动态数组取 ( 元素数、 容量)可推出同样的 。
三种方法是同一件事的三种视角:聚合最直接,记账最直观,势能最一般化(适合复杂的结构如斐波那契堆)。
均摊不等于每次
Section titled “均摊不等于每次”均摊 的意思是总代价 ,不是每次 。单次扩容仍要付 ——这在两个场景必须显式处理:
- 实时系统:偶发的 停顿不可接受。对策:增量式扩容(每次操作迁移固定数量的元素,把 摊到每次操作上,单次最坏 )或预分配。
- 延迟敏感服务:哈希表 rehash 的停顿会打进 p99(→ 性能工程与可观测性)。Redis 的渐进式 rehash 是标准答案(→ 哈希表)。
均摊分析的适用边界
Section titled “均摊分析的适用边界”均摊分析的对象必须是一串操作,且昂贵操作不能被无限触发而不带中间的便宜操作。两个经典结果:
- 二进制计数器:从 0 加到 ,翻转的总位数 ,均摊每次 ;
- 斐波那契堆:decrease-key 均摊 的证明必须用势能法,因为代价的结构复杂(级联剪切)。
标准库的均摊保证在并发下要小心:动态数组倍增时读者会看到中间状态,因此并发容器要么锁整个结构,要么放弃均摊保证换无锁操作。
- 均摊分析看一串操作的总代价:倍增扩容总拷贝 ,追加均摊
- 三种方法:聚合(直接求和)、记账(预付)、势能(最一般化)
- 均摊 不等于每次 :单次扩容仍付
- 实时/延迟敏感场景用增量式扩容把停顿摊薄
- 势能法适合复杂结构(斐波那契堆);并发下均摊保证需要重新审视