一,基本原理
Huffman编码是一种常用的压缩算法,下面我将使用一个例子来演示一遍如何根据字符出现的概率来对字符进行编码。我们知道Huffman编码是可变字长编码的一种。为了实现压缩,自然地要给出现概率大的字符配以短编码,概率小的就配以长编码。现在我们假设有5个字符以及它们假设出现的概率:
a | b | c | d | e |
0.2 | 0.15 | 0.25 | 0.15 | 0.25 |
我们就要对这5个字符进行Huffman编码。大致步骤如下:
1)新建一个空的优先级队列F,然后将分别包含这5个字符及其概率的5个节点按照概率大小,依序插入到队列F中。
2)循环从队列中选概率最小的两个节点概率相加。例如:当前队列中概率最小的两个节点为“b”和“d”,新建一个节点,新节点中的字符串值为“bd”,得到的和0.3作为新节点的概率。最后,要用新节点“bd”的左子节点和右子节点分别是这两个概率最小的节点,然后从队列中删掉这两个最小概率的节点,并将新节点“bd”插入到队列F中。注意:插入时要按照概率依序插入,此时队列里的所有节点应该是下面这样排列的(从小到大):
a | c | e | bd |
0.2 | 0.25 | 0.25 | 0.3 |
下一个循环当然就是选取a和c的概率相加啦~然后用新节点的leftChild和rightChild分别指向“a”和“c”,然后删掉它们两并新建一个“ac”节点插入到队列F中。
。。。
最后直到队列中只剩下一个节点时(即为根节点“abcde”)循环结束。此时Huffman树已经建好。
3)依据从根节点开始自上而下找包含对应字符的字符串节点,在每一层判断是左子节点包含还是右子节点包含时,如果往左就配0,往右就配1。一直到叶节点时(即找到对应的字符了)停止编码,这样就得到该字符对应的Huffman编码啦~
二,具体实现
使用基于链表实现的优先级队列存储所有的节点,每个节点的data域有字符串 str 和对应的出现概率 probability。
首先,我们先来实现一个Node类吧~
public class Node {
String str;
double probability;
Node leftChild=null;
Node rightChild=null;
Node next=null;
public Node(String s,double p) {
this.probability=p;
this.str=s;
}
public void display(){
System.out.print(probability+" ");
}
}
在上面这段代码中新建了一个Node类,str和probability在上面已经提到了,leftChild和rightChild是为后面建立树时准备的,next则是为了实现一个基于链表的优先级队列。每次新建一个Node对象就代表一个字符节点。例如:Node a=new Node("a",0.2);
为了方便进行Huffman树的建立,我们需要实现优先级队列来存储那5个字符节点,下面是对应的代码:
public class PriorQueue {
private static int nElem=0;
private Node first;
private Node last;
public PriorQueue(){
this.first=null;
this.last=null;
}
public boolean isEmpty(){
return first==null;
}
public Node addElement(String str,double p){//添加新节点
if(isEmpty()){ //添加前队列为空
first=new Node(str,p);
last=first;
nElem++;
return first;
}else{
Node current=first;
Node previous=first;
Node newNode=new Node(str,p);
while(current.probability
public class Tree {
PriorQueue F=new PriorQueue(); //新建一个优先级队列F对象,用来存放字符节点
Node n1,n2,newNode,root;
String newStr;
double newP;
public void treeBuild(){ //建立Huffman树
F.addElement("a", 0.2);
F.addElement("b", 0.15);
F.addElement("c", 0.25);
F.addElement("d", 0.15);
F.addElement("e", 0.25);
F.displayAll();
while(F.getnElem()>1){
n1=F.deleteElement();
n2=F.deleteElement();
newStr=n1.str+n2.str;
newP=n1.probability+n2.probability;
newNode=F.addElement(newStr, newP);
F.displayAll(); //显示每次添加入新的节点后的队列F
newNode.leftChild=n1;
newNode.rightChild=n2;
}
root=F.deleteElement();
}
public void HuffmanCode(Node localRoot,char c){
if(localRoot.leftChild!=null&&localRoot.leftChild.str.indexOf(c)!=-1){
System.out.print("1");
HuffmanCode(localRoot.leftChild,c);
}else if(localRoot.rightChild!=null&&localRoot.rightChild.str.indexOf(c)!=-1){
System.out.print("0");
HuffmanCode(localRoot.rightChild,c);
}
}
public void CodePrint(char c){
System.out.println(c+"的Huffman编码为:");
HuffmanCode(root,c);
System.out.println();
}
}
在Main.java中新建一个Tree的对象,然后进行树的建立以及最后输出每个字符对应的Huffman编码。
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Tree tree=new Tree();
tree.treeBuild();//调用treeBuild()进行树的创建
tree.CodePrint('a');//显示对于的Huffman编码
tree.CodePrint('b');
tree.CodePrint('c');
tree.CodePrint('d');
tree.CodePrint('e');
}
}
程序运行结果如下:
三,关于压缩方面的讨论
Huffman编码它实现了压缩。对于a,b,c,d,e 这5个字符,至少要用3 bits与表示。例如:对于“ace”这个单词,我们就要有9 bits去表示它。但经过Huffman编码后,对于“ace”我们只用6 bits去表示,压缩率为 9/6=1.5。