转载自: http://wenku.baidu.com/view/0ee67e08763231126edb112d.html
OABST:
Hu Tucker算法构造一棵Optimal Alphabetic Binary Search Tree (OABST). 和Huffman树最优性要求一样, 都是最小化外部结点(叶子)到根的带权路径总长. 区别是OABST的外部结点要求有序. 即每次合并要求节点是相邻的
Hu-Tucker算法分为三个阶段:
阶段1, 组合:
先不考虑有序性, 构造一棵最优树, 使得所有叶子的深度和最终所求的OABST的叶子的深度一样且最优性一样.
定义工作序列是一个可以包含内部或外部结点的序列.
开始时, 把所有外部结点按顺序放在工作序列内.
在每次的迭代中,选出两个结点wi, wj合并为一个内部结点w=wi+wj, 来替换结点wi, 从工作序列中删去原来的wj, 形成一个新序列.
称每次的迭代被组合的点对(wi, wj), 为Local Minimum Compatible Pairs(LMCP). 每次迭代的选择的LMCP是唯一的, 它满足如下4条规则:
规则(1): 在工作序列中, 结点wi到结点wj之间没有外部结点(最重要的规则);
规则(2): 它们的权和w=wi+wj要最小;
规则(3): 在满足(1)(2)后,下标i要最小;
规则(4): 在满足(1)(2)(3)后, 下标j要最小.
以后统一用方框表示外部结点, 用圆圈表示内部结点.
下面举例, n = 5, w = (5, 2, 2, 3, 6). 注意: 在第(2)步时, LMCP(5,3)跨越了内部结点(2+2).
本阶段的实现最为复杂, 也最为重要, 下面介绍实现. 观察算法, 发现连续的内部结点之间的顺序没有意义. 即两个相邻外部结点间的内部结点, 相当于做局部的Huffman树的贪心.可以看成, 外部结点将整个工作序列划分为若干的段, 两个相邻外部结点间的内部结点, 组成一个内部结点集合(用虚圆圈表示内部结点集合).
由于外部结点与内部结点集合交替出现, 可以用一个块结构(用外框表示)把外部结点与其右方的内部结点集合包含进来. 再将这些块结构用双向链表串起来.
下面讨论, LMCP可能出现的情况:
设图中的块结构B为当前块结构.
情况(1): 在当前块结构中, 外部结点与最小内部结点.
合并LMCP后, 放入A的内部结点集合, 同时将B的内部结点集合合并到A中.
情况(2): 在当前块结构中的外部结点, 与右边块结构中的外部结点.
合并LMCP后,放入A的内部结点集合, 同时将B, C的内部结点集合合并到A中.
情况(3): 在当前块结构的内部结点集合中, 最小与次小的内部结点.
合并LMCP后,放入B的内部结点集合.
情况(4): 在当前块结构中的最小内部结点, 与右边块结构中的外部结点.
合并LMCP后,放入B的内部结点集合, 同时将C的内部结点集合合并到B中.
内部结点集合需要支持找最小和次小元素与合并操作的数据结构来维护. 常见的可并优先队列: 左偏树Leftist Tree, 配对堆Pair Heap, Fibonacci Heap 均可胜任.
每次要找一个全局最小的点对LMCP, 则需要用一个总的优先队列来维护这些点对. 由于块结构的合并,会造成大量合法的点对的改变, 这要求总的优先队列支持插入删除操作. 在点对优先级上, 点对之间的比较关系为: 以点对权值和为第1关键字, 以当前结构的编号作为第2关键字, 最后以情况的编号做为第3关键字.
这样每种情况的维护都可以在O(logn)的时间内解决.
整个阶段用时O(nlogn)
阶段2, 深度的确定:
遍历求出执行第1阶段后的所有外部结点的在最优树中的深度. 实现时, 记录下阶段1中每个结点(外部结点和内部结点都有)的儿子, 递规遍历一遍, 即可求得.
整个阶段用时O(n)
阶段3, 重新组合:
抛弃阶段1和阶段2的内部结点, 重新构造树, 得到OABST.
经过阶段1和阶段2, 可得到一个有深度标号的工作序列.
不断地选出两个最大相同深度d的相邻结点wi, wj合并成一个深度d-1的结点,并替换wi的位置. 严格地说, 选择的点对满足:
规则(1): wi, wj在工作序列中要相邻;
规则(2): di, dj在所有剩下结点中是最大的;
规则(3): 在满足(1)(2)后,下标i要最小.
以上选择的结点, 深度必相同即di = dj. 实际上, 通过阶段1,2产生的深度标号, 才能正确的执行阶段3, 不是任意一种深度标号都可以.
实现上, 使用一个队列和一个栈, 按如下算法执行即可.
1. 将工作序列按顺序存入队列. 栈清空.
2. 若2个栈顶元素有同样的深度标号d, 将它们出栈, 合并成一个深度为d-1的内部结点, 入栈, 转2; 否则, 转3.
3. 若队列非空, 将队列头入栈, 转2; 否则, 结束.
整个阶段用时O(n)