1.实验原理
自适应哈夫曼编码这种方案在不需要事先构造 Huffman 树,而是随着编码的进行,逐步构造 Huffman 树。随着编码的进行,同一个符号的权重发生改变,此时原有的Huffman编码树已经不一定符合具有最小加权路径这一条件,因此必须调整编码树,保持其合法Huffman编码树的状态。
在说明调整方法之前,先给每个节点引入两个新的属性:“节点编号”和“所属块”。节点编号时全局一个唯一的值,根节点的编号最大,向左和向下递减。如下图:
“块”是指具有相同权重的一组结点。
在构造动态霍夫曼编码树的过程中,需要遵循两条重要原则:
(1)权重值大的节点,节点编号也较大。
(2)父节点的节点编号总是大于子节点的节点编号。
以上两点称为兄弟属性(sibling property)。在每一次调整节点权重值时,都需要相应的调整节点编号,以避免兄弟属性被破坏。在对某一个节点权重值进行“加一操作”时,应该首先检查该节点是否具有所在的块中的最大节点编号,如果不是,则应该将该节点与所在块中具有最大节点编号的节点交换位置。然后再对节点的权重值加。这样,由于该节点的节点编号已经处于原来所属块中的最大值,因此权重值加一之后兄弟属性仍然得到满足。最后,由于节点的权重发生了变化,必须递归地对节点的父节点进行加一操作。
初始化编码树时,编码树的初始状态只包含一个叶节点,包含符号 NYT(Not Yet Transmitted,尚未传送),权重值为 0。 NYT是一个逸出码(escape code),不同于任何一个将要传送的符号。当有一个尚未包含在编码树中的符号需要被编码时,系统就输出 NYT 编码,然后跟着符号的原始表达。当解码器解出一个 NYT 之后,它就知道下面的内容暂时不再是 Huffman 编码,而是一个从未在编码数据流中出现过的原始符号。这样,任何符号都可以在增加到编码树之前进行传送。
在需要插入一个新符号时,总是先构造一个新的子树,子树包含NYT 符号与新符号两个叶节点,然后将旧的NYT 节点由这个子树替代。由于包含NYT 符号的节点权重值为0,而包含新符号的叶节点的权重值为1,因此最终效果相当于原NYT 节点位置的权重值由0 变为1。因此,下一步将试图对其父节点执行权重值“加一操作”。
对符号编码的方法与静态霍夫曼编码一致,每次符号编码完成以后,也将对包含符号的节点权值进行加一操作。
将一个新的符号插入编码树或者输出某一个已编码符号后,相应的符号的出现次数增加了1,继而编码树中各种符号的出现频率发生了改变,不一定符合兄弟属性,按照上述方法进行调整,使其符合要求。
2. 实验流程
这是别人的流程图,也可以参考。
3.数据结构
Node
public class Node {
public char letter; // the character of each code
public int count; // 权重
public int left; // the left child of each node
public int right; // the right child of each node
public int parent; // the parent of each node
}
HuffmanTree