哈夫曼编码是一种被广泛应用而且非常有效的数据压缩技术,一般用来压缩文本和程序文件。哈夫曼压缩属于可变代码长度算法一族。意思是个体符号(例如,文本文件中的字符)用一个特定长度的位序列替代。因此,在文件中出现频率高的符号,使用短的位序列,而那些很少出现的符号,则用较长的位序列。
哈夫曼编码是由一颗满二叉树来实现的,即哈夫曼树。构造哈夫曼树非常简单,将所有的结点放到一个队列中,用一个结点替换两个频率最低的结点,新结点的频率就是这两个结点的频率之和。这样,新结点就是两个被替换节点的父结点了。如此循环,直到队列中只剩一个结点(树根)。一颗哈夫曼树的构造过程如下所示(图中字母如f、e、c,表示被压缩的符号,数字如5、9、12,表示对应符号出现的频率):
其贪心性质就体现在合并两个结点时总是选取当前所有结点中最小的两个(合并后那两个结点就从候选序列中去掉了)。这时最小优先队列就是很理想的处理方式。整个算法的java实现如下:
import java.util.PriorityQueue;
/** 哈夫曼编码 */
public class Huffman {
static PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
static int[] C;
static Node[] N;// 分支结构
static Node root;// 根结点
private static class Node {
int key;
Node left;
Node right;
public Node(int q) {
this.key = q;
}
}
// 初始化优先级队列,这里直接利用了Java内置的PriorityQueue
public Huffman(int[] E) {
C = E;
N = new Node[C.length - 1];
for (int i = 0; i < C.length; i++) {
queue.offer(C[i]);
}
}
// 建立Huffman树结构
public void bulid() {
// 每一个node都是一个独立的分支
for (int i = 0; i < N.length; i++) {
N[i] = new Node(0);
}
for (int i = 0; i < N.length; i++) {
// 此处就是贪心算法的体现,选取当前看来最优的选择
N[i].left = new Node(queue.poll());
N[i].right = new Node(queue.poll());
N[i].key = N[i].left.key + N[i].right.key;
queue.offer(N[i].key);
}
// 把这些分支合并组成树结构(以N的最后一个元素为根结点)
for (int i = N.length - 1; i >= 1; i--) {
// 检查接下来的所有对象是不是它的左右结点
for (int j = i - 1; j >= 0; j--) {
if (N[i].left.key == N[j].key) {
N[i].left = N[j];
}
if (N[i].right.key == N[j].key) {
N[i].right = N[j];
}
}
root = N[N.length - 1];
}
}
// 构造Huffman编码
public void code(String code, Node node, int i) {
if (node == null)
return;
code(code + "0", node.left, i);
code(code + "1", node.right, i);
if (C[i] == node.key) {
System.out.println(i + "(" + C[i] + ")" + "霍夫曼编码为" + code);
}
}
public void printCode() {
for (int i = 0; i < C.length; i++) {
code("", root, i);
}
}
public static void main(String[] args) {
Huffman hfm = new Huffman(new int[] { 9, 45, 12, 13, 5, 16 });
hfm.bulid();
hfm.printCode();
}
}