引言:在不知道总共有多少元素的情况下,一个哈希表应该多大?一个方法是动态表dynamic table。当数据溢出的时候double表格。将原来数据移动到新表,并释放旧表。
- 那么如何分析每一次插入操作所需的赋值次数呢?对于这个问题最简单直观的方法是用聚集方法(aggregate analysis),即不直接分析每一次的赋值次数而分析n次插入的赋值次数。
具体分析如下:
观察可知
C=∑i=1nCi=n+∑j=0lg(n−1)2j≤3n=θ(n) - 第二种分析方法是会计方法(accounting argument),这个方法和第三种势能方法相对于聚集方法更加准确的分析每一步操作的平摊成本。这种方法假设自己是一个会计,对每一步操作都收取平摊费用,每次收取的平摊费用除了用于当次操作,剩下的钱存入银行,用于下一次操作,这就要保证银行的钱一直是正的。对于动态表插入问题,只需假设每一步的平摊费用是3元,其中1元用于当次赋值,1元用于下一次赋值到新表,1元用于承担旧有的项赋值到新表。同样可以得出3n的结果。方法的关键在于确定每一步的平摊费用。
第三种方法是势能方法(potential function method),势能方法将会计方法中的“银行账户”定义为势能。要解释清楚这种方法先做以下假设。
- 从数据结构
D0
开始,操作
i
将
Di−1 转化为 Di - 每次操作的成本是 Ci
- 定义势能函数 Φ,Φ:{Di}−→IR
- Φ(D0)=0
- Φ(Di)≥0
- 从数据结构
D0
开始,操作
i
将
那么平摊成本可以如下表示:
Cˆi=Φ(Di)−Φ(Di−1)+Ci
如果 ΔΦi>0 表示平摊成本过多,操作 i 将额外的能量存储在数据结构Di 中,用于之后的操作;如果 ΔΦi<0 表示平摊成本不足,那么数据结构 Di−1 中的能量就拿出来用于操作。这样就可以通过平摊成本去估计算法实际操作的成本。方法的可行性描述如下。
∑i=1nCˆi=∑i=1n[Φ(Di)−Φ(Di−1)+Ci]=∑i=1nCi+Φ(Dn)−Φ(D0)≥∑i=1nCi
所以这个方法的关键是想办法找到问题对应的 Φ 。对于动态表它的 Φ(Di)=2i−2⌈lgi⌉ ,这个不容易想到,所以对于动态表问题,用前两种平摊分析方法优越于势能方法。
但是,在竞争分析(Competitive analysis)中,势能方法体现了强大而优美的作用,这里面以自组织表(self-organizing lists)为例,详细解释这种强大的分析方法,自组织表有点像搜索引擎的索引。本文讨论的自组织链表基于以下假设
- 一个有
n
个元素的表
- 访问元素
- 表可以通过相邻元素的移项进行重排,每次移项的成本是1。
在对表访问的时候,我们希望被访问的元素尽可能靠近表头,从而加快访问的速度,因此在每次访问某元素后都要对表格作一定的调整。那么怎样调整呢?为了方便的讨论不同方法调整的效果,下面引入两个定义。
- 在线算法(online algorithm),每次操作仅提供操作序列
- 离线算法(off-line algorithm),可以看到整个操作序列,从而根据每一次访问和已知的未来访问序列,产生最好的调整方法。
一个直觉的想法是,记录每个元素被访问的次数,从而使得表中的元素根据访问次数从高到低进行排序。但这样的操作似乎太费劲了。在实际引用中,一般是使用MTF策略,即每次访问一个元素
x
,访问完了之后将它移动的到表头。它的成本是
- 一个在线算法是
α
Competitive,如果存在常数
k
,使得算法在操作序列
- Proof
假设
那么元素 x 在
用MTF当请求访问 x 的时候生成了
综上,MTF是4-competitive的。在实际应用中,将表中最后一个元素移动到表头,只要简单指针操作,因此可以认为这个操作free。在这种情况下,Sleator and Tarjan 给出了理论证明,证明此时MTF是2-Competitive的。 总结:势能方法应用的关键是定义一个好的势能函数,而势能函数的定义又跟问题本身密切相关,本文中的关键应该是用逆序对去描述两个表的差异,从而引出势能函数,然后顺利的用集合去描述元素 x <script type="math/tex" id="MathJax-Element-7766">x</script>的位置。
参考文献:
DANIEL D. SLEATOR and ROBERT E. TARJAN, Amortized Efficiency of List Update and Paging Rules, Programming Techniques and Data Structures 202-208.