动态表
概述
动态表(Dynamic Table)是一种能够自动调整大小的数组实现,支持高效的插入和删除操作。当表满时进行扩张(通常倍增),当表过于稀疏时进行缩容。通过摊还分析可以证明,在合理的扩张/缩容策略下,每次操作的摊还代价为 。
定义
动态表
动态表 是一个支持动态调整大小的数组,维护以下状态:
- :当前存储的元素个数
- :当前分配的总槽位数
- :负载因子(load factor)
支持两种基本操作:
TABLE-INSERT(T, x):插入元素TABLE-DELETE(T, x):删除元素
核心性质
- 负载因子: 是衡量表利用率的指标。理想情况下 接近 ,但需要为插入留出空间。
- 几何扩张策略:当表满()时,分配一个更大的表(通常为原来的 倍),将所有元素复制到新表中。单次扩张代价为 ,但摊还代价为 。
- 防抖动设计:缩容阈值设为 (而非 ),避免在插入和删除交替进行时出现反复扩张和缩容的”抖动”(thrashing)现象。这确保了连续扩张和缩容之间有足够的”缓冲区”。
- 摊还 保证:通过合理的势能函数设计,可以证明在仅插入或插入+删除场景下,每次操作的摊还代价均为 。
表扩张机制
当执行 TABLE-INSERT 且当前表已满时:
- 分配新表,大小为
- 将旧表中所有 个元素复制到新表
- 插入新元素
- 释放旧表
代价分析:扩张操作的代价为 (复制所有元素),但通过摊还分析,每次插入的摊还代价仅为 。
表缩容机制
当执行 TABLE-DELETE 且负载因子降至 时:
- 分配新表,大小为 (但至少为 )
- 将剩余元素复制到新表
- 释放旧表
防抖动原理:缩容阈值为 而非 。假设刚缩容后 (因为 ,缩半后 )。要触发下一次扩张需要将表填满(),即需要再插入 个元素;要触发下一次缩容需要删除到 ,即需要再删除 个元素。这保证了在两次调整之间有 次操作。
势能函数设计
仅插入场景
定义势能函数:
验证:
- 初始状态:, ✓
- 非负性: 时 ; 时(刚扩张后) ✓
分析:
- 无扩张时:
- 有扩张时:
每次插入摊还代价为 。
插入+删除场景(分段势能函数)
定义分段势能函数:
该函数在 处连续(值为 ),且始终非负。可以证明插入和删除的摊还代价均为 。
实际编程语言实现
| 语言/数据结构 | 初始容量 | 扩张因子 | 缩容策略 |
|---|---|---|---|
Python list | 0(动态分配) | (约 ) | 按比例缩容 |
Java ArrayList | 10 | 缩至当前 | |
C++ std::vector | 0 | C++11 起 shrink_to_fit | |
Go slice | 0 | 无自动缩容 |
实现差异
Python 使用 而非 的扩张因子,牺牲了摊还常数因子以换取更小的内存开销。C++ 的 扩张在理论上给出最优的摊还常数,但实际内存利用率可能较低。