一、定义
霍夫曼(Huffman)编码是一种编码方式,主要用于数据文件的压缩。它的主要思想是放弃文本文件的普通保存方式:不再使用7位或8位二进制数表示每一个字符,而是用较少的比特表示出现频率高的字符,用较多的比特表示出现频率低的字符。
引例:假设需要对文本字符串“ABRACADABRA!”编码
一种方式是,用较短的比特表示所有可能的字符。
如A-0、B-1、R-00、C-01、D-10、!-11,这样“ABRACADABRA!”的编码就是0 1 00 0 01 0 10 0 1 00 0 11。这种表示方法只用了17位,而7位的ASCII编码则用了77位。但是这种方法存在一个问题:当不存在分隔符的时候,我们无法根据一连串比特码区分字符与比特码的映射关系。如01000010100100011也可以表示成CRRDDCRCB或其它字符串。
第二种方式是,如果任一字符的编码都不是其它字符编码的前缀,那么就不需要分隔符了。
如A-0、B-1111、R-1110、C-110、D-100、!-101。而霍夫曼编码就是寻找这种变长前缀的算法,且能使最终构造出的比特流最小。
二、实现方式
霍夫曼编码,首先需要根据输入文本,构造一棵二叉树,树的左链接表示比特"0",右链接表示比特"1",叶子结点表示字符。字符所对应的霍夫曼编码值就是从根结点到叶子结点的链接值。
总体实现步骤如下:
【压缩步骤】
压缩用于将原始文本转换成一条编码过的比特流。
读取输入;
统计输入中每个字符的频次;
根据频次,构造Huffman树;
构造编译表,用于将字符与变长前缀映射;
将Huffman树编码为比特字符串,并写入输出流;
将文本长度编码为比特字符串,并写入输出流;
压缩数据,即使用编译表翻译每个文本字符,写入输出流。
【解压缩步骤】
解压缩用于将一条编码过的比特流转换为原始文本。
读取Huffman树(编码在比特流的开头);
读取需要解码的字符数量;
根据步骤1还原的Huffman树解码压缩数据。
三、源码实现
3.1 构造Huffman树
构造一颗Huffman树的步骤如下:
1、遍历一遍文本,统计各个字符出现的频次;
2、对每个字符构造一个叶子结点,结点包含该个字符的频次;
3、每次从所有结点中选出频次最小的两个根结点,构造一个新结点,新结点作为根结点,两个根结点作为其左右子结点,新结点的频次为左右子根结点的频次和;
4、重复第3步,直到最后只剩一个根结点。
树结点定义:
private static class Node implements Comparable {
private final char ch; //字符
private final int freq; //每个结点保存以该结点为根的子树中的字符数量
private final Node left, right;
Node(char ch, int freq, Node left, Node right) {
this.ch = ch;
this.freq = freq;
this.left = left;
this.right = right;
}
private boolean isLeaf() {
return (left == null) && (right == null);
}
public int compareTo(Node that) {
return this.freq - that.freq;
}
}
构造Huffman树:
//构造Huffman树