原理
http://bitjoy.net/2016/08/18/the-implementation-of-huffman-code/
哈弗曼编码是一个很经典的压缩算法,压缩率能达到50%,甚至更低。它的基本原理包括四个步骤:
1. 统计文件中每个字符出现的频率。
2. 构建一个哈弗曼树。建树的过程是不断的合并频率最小的两个节点,父亲节点的频率为两个孩子节点的频率之和。如此循环直到合并成一个根节点。叶子节点为不同的字符及其频率。
3. 生成哈弗曼编码。从树根开始对树进行编码,比如进入左孩子的边标记为0,进入右孩子的边标记为1,这里的0和1都是二进制位。这样之后,每个叶子节点都有一个唯一的二进制编码,这就是哈弗曼编码。频率越低的字符哈弗曼编码越长,频率越高的字符哈弗曼编码越短,这样就能起到压缩的效果。
4. 第二遍扫描文件,把字符转换为对应的哈弗曼编码,保存成压缩文件。
解压缩的过程就是解析二进制位,然后查找哈弗曼树,每找到一个叶子节点,就解析出一个字符,直到解析完所有二进制位。
那么如何构造哈夫曼树呢
如何构造哈夫曼树呢?
由于赫夫曼树中没有度为1的节点,则一棵具有n个叶子节点的的赫夫曼树共有2n-1个节点( 具体原因请看链接 http://blog.csdn.net/yuzhiyun3536/article/details/77720771 ),因此可以将这些节点存储在大小为2n-1的一维数组(我的下面代码中用的是ArrayList,当然其实是一样的)中。
首先把那些叶子节点存进数组,下标范围为( 0到n-1 )
之后一个for循环,i从n开始不断找出 下标为0到 i-1 之间 的两个weight最小的节点(s1 和 s2),然后构造一个父亲节点,并且更新这个父亲节点以及s1 和 s2对象的成员变量(left, right, parent),最后把这个节点添加加进数组。
慢慢地,这个数组就填充满了。哈夫曼树也就构造出来了。
代码
//数组里面的权值对应了空格以及abcd......xyz,然后进行Huffman编码
package tree;
import java.util.ArrayList;
public class testHuffmanTree {
public static void main(String[] args) {
//the weight of leafNode
int weight[]={186,64,13,22,32,103,21,15,47,57,
1, 5,32,20,57, 63,15, 1,48,51,
80,23,8,18,1,16,1};
//构造哈夫曼树
HuffmanTree tree =new HuffmanTree(weight);
//生成哈夫曼编码
tree.HuffmanCoding();
//打印
tree.printHuffmanCoding();
}
}
class HuffmanTreeNode{
int weight;
//可以看出我们仅用下标来表示,而不是对象引用的形式。
int parent,lchild,rchild;
huffmanTreeNode(){}
huffmanTreeNode(int weight,int parent,int lchild,int rchild){
this.weight=weight;
this.parent=parent;
this.lchild=lchild;
this.rchild=rchild;
}
}
class HuffmanTree{
private int n;//the number of leafNodes
private int m;
private int[] weight;
/*the number of all Nodes
*不能在这里写m=2*n-1;否则m只是会等于1.
*/
//存储哈夫曼树的节点,其中0~n-1存储叶子节点(也就是最关键的)
private ArrayList<huffmanTreeNode> huffmanTree=new ArrayList<huffmanTreeNode>();
//存储每个叶子节点的编码
private char[][] Coding;
huffmanTree(){}
huffmanTree(int[] weight){
this.weight=weight;
//如果m,n 不在这里初始化,在其他函数中初始化是会随即销毁的
n=weight.length;
m=2*n-1;
creatHuffman();
}
public ArrayList<huffmanTreeNode> getHuffmanTree() {
return huffmanTree;
}
/*
*选出权值最小的节点下标,范围为0~ endOfArrayList
*只考虑节点的parent为0的节点,因为不等于0就表示这个节点已经有父亲了,不能再参与选择
*
*/
int Select(int endOfArrayList){
int minimun=1000000000;
int current=-1;
for(int i=0;i<=endOfArrayList;i++){
if(0!=huffmanTree.get(i).parent)
continue;
if(huffmanTree.get(i).weight<minimun){
minimun=huffmanTree.get(i).weight;
current=i;
}
}
return current;
}
//creatHuffman
void creatHuffman(){
for(int i=0;i<n;i++){
huffmanTree.add(new huffmanTreeNode(weight[i],0,0,0));
}
//parent初始化为0,在选择最小权值节点的时候,这个可以用来做判断条件,不为0 就表示已经有父亲了,不再参与选择
for(int i=n;i<m;i++){
huffmanTree.add(new HuffmanTreeNode(0,0,0,0));
}
for(int i=n;i<m;i++ ){
int s1,s2;
s1=Select(i-1);
huffmanTree.get(s1).parent=i;
s2=Select(i-1);
huffmanTree.get(s2).parent=i;
//创建新的节点
HuffmanTreeNode parentNode=new HuffmanTreeNode(0,0,0,0);
parentNode.lchild=s1;
parentNode.rchild=s2;
parentNode.weight=
huffmanTree.get(s1).weight+
huffmanTree.get(s2).weight;
//把这个新的节点加进list
huffmanTree.add( parentNode );
}
}
//getHuffmanCoding
void HuffmanCoding(){
Coding=new char[n][n];
for(int i=0;i<n;i++){
int start=n-1;
for(int c=i, f=huffmanTree.get(i).parent; f!=0; c=f,f=huffmanTree.get(f).parent){
if(huffmanTree.get(f).lchild==c)
Coding[i][start--]='0';//左分支为‘0’
else
Coding[i][start--]='1';
}
}
}
//printHuffmanCoding
void printHuffmanCoding(){
char letter=97;//对应的字母
System.out.println("the codes are as following ******" );
for(int i=0;i<n;i++){//第一个是空格,额外考虑
if(i!=0){
System.out.print(letter+" : " );
letter++;
}
else
System.out.print("空格"+" : " );
for(int j=0;j<n;j++){
if(Coding[i][j]!='#')
System.out.print(Coding[i][j]+" ");
}
System.out.println();
}
}
}