huffman编码是重要的编码方式之一。作为一名程序员,应该要做到融会贯通,因此我在网上搜些资料看,发现几乎没有适合我等菜鸟级的文章,不是没有图解,就是不说明为什么要引入huffman编码,稀里糊涂,于是自己整理了一下,欢迎诸君拍砖。
Huffman是一种无损压缩方法:所谓无损就是不会有信息丢失,可以100%恢复原信号,像奈奎斯特恢复就是有损了;所谓压缩,就是可以占有更少的存储空间。
比如要发送三个字符:S1,S2,S3。我们可以这样编码:00,01,10。当然这是小数据,如果要发送的图片或是视频呢?比如一副高清图片,10M,在局域网上即使可以达到理论的10Mbps,也要1s,没法满足视频的最低帧频24fps。因此压缩非常重要,当然采用的压缩很多,huffman只是早期的一种而已,这就是Huffman的背景。
现在来看看它的原理:先不联系最优二叉树那套东西。基本原理是根据字符出现的概率确定其码子。即高概率的少码字,低概率的多码字。 举个例子吧:
图 1
结合上图:
A B C D E 5个字符及其出现的概率。过程很简单:
First: 从上到下,根据概率由大到小排列;
Second: 俩俩合并,先合并概率低的,比如D E 概率最低,合并之,得到概率0.1,再寻找概率最低的俩部分,发现是C 和 DE,合并之,合并后,
标出新的 概率值 ,如图中红色圈起来的部分,直到合并完全;
Third: 标出0或1。如图中蓝色部分,低概率的给0(1也可以),概率相等的,就0和1随意了;
Last: 得到编码值:A:1,B:10;C:100; D:1000; E:0000。
整个编码过程完毕。以上就是Huffman编码的大体过程。好了,感受完其最基本的过程。我们再来完成最优二叉树(赫夫曼树)的代码编写,分为俩部分:第一部分为基本概念,第二部分为:代码的实现,并有测试结果。
huffman 树:带权路径最短的树。所谓带权路径,就是从该节点到树根的长度与节点上的权的乘积
图 2
那么树的带权路径就是所有节点的带权路径之和,公式表示:
如上图:套公式:6*1+4*2+1*3+2*3
有了一开始部分的介绍,huffman二叉树的构造就很简单了,步骤:
1,找到权重最小的俩节点,合并最为一个新的节点,比如上面D,E合并,得到一个新的节点,暂且叫 做DE,则D,E作为子节点,DE作为父节点;
2,以DE代替D和E,DE的权重为D和E之和,重新第一步,直到所有节点都加入到二叉树中。
3,对于权重小的节点附0(或1),对于权重大的节点附1(或0)。
如把图1huffman的计算过程变成二叉树的 图形如下:
图 3
读者,把一开始的图1 ,旋转过来跟图 2 是一样的,因为原理一样。注意:huffman叶节点为n,需要2*n-1个节点来构成二叉树,树的节点的度为2(除去叶节点)。
第二部分:以一个例子开始: 权重W={5,29,7,8,14,23,3,11}.n=8.此例子为严蔚敏第159页的例子。
上代码:
/* Coder Information e_mail:shenganbeiyang@163.com QQ:501968942 */ #ifndef HUFF_H #define HUFF_H #include<string> //定义一个结构体,包括节点的父节点和左右子节点 //字符的个数 const int maxnum=30; typedef struct Tnode { char val; int weight; int parent; int lchild; int rchild; }huffmanNode ,*huffmanTree; class huffmanCoding { public: //初始化最优二叉树(哈夫曼树) huffmanCoding(huffmanNode * hn,int length); //编码过程 void Selection(int); void Encode(int); //解码过程 // string deCode(); //deconstruction function ~huffmanCoding(){ for(int i=0;i<length;++i) delete HC[i]; delete [] HC; }; char** HC;//保存编码信息 private: huffmanNode InitData[maxnum]; //char** HC;//保存编码信息 int length; }; //constructor function.初始化二叉树 inline huffmanCoding::huffmanCoding(huffmanNode * hn,int length_) { HC=new char*[length_];//(sizeof(char*)*length);//new (char*)[length]; length=length_; int i; for( i=0;i<length;++i) { InitData[i]=hn[i]; InitData[i].parent=-1; InitData[i].rchild=-1; InitData[i].lchild=-1; } for(int j=i;j<2*length;++j) { InitData[j].lchild=-1; InitData[j].rchild=-1; InitData[j].weight=-1; InitData[j].val='0'; InitData[j].parent=-1; } } //编码huffman二叉树 inline void huffmanCoding::Selection(int codedlength) { int pos_1,pos_2; pos_1=pos_2=-1; int i; for( i=0;i<codedlength;++i) { if(InitData[i].parent==-1) { pos_1=i; break; } } for(int k=pos_1+1;k<codedlength;++k) { if(InitData[k].weight<InitData[pos_1].weight&&InitData[k].parent==-1) { pos_1=k; } } for(int j=0;j<codedlength;++j) { if(InitData[j].parent==-1&&j!=pos_1) { pos_2=j; break; } } for(int j=pos_2+1;j<codedlength;++j) { if(InitData[j].weight<InitData[pos_2].weight&&InitData[j].parent==-1&&j!=pos_1) { pos_2=j; } } if(InitData[pos_1].weight<InitData[pos_2].weight) { InitData[codedlength].lchild=pos_1; InitData[codedlength].rchild=pos_2; InitData[pos_1].parent=codedlength; InitData[pos_2].parent=codedlength; InitData[codedlength].weight=InitData[pos_1].weight+InitData[pos_2].weight; } else { InitData[codedlength].lchild=pos_2; InitData[codedlength].rchild=pos_1; InitData[pos_1].parent=codedlength; InitData[pos_2].parent=codedlength; InitData[codedlength].weight=InitData[pos_1].weight+InitData[pos_2].weight; } } inline void huffmanCoding::Encode(int len) { for(int i=len;i<2*len-1;++i) { Selection(i); } char*dc=new char[length]; for(int m=0;m<length;++m) dc[m]='\0'; int start=0; int tmp; int p; for(int m=0;m<length;++m) { for(tmp=m,p=InitData[m].parent;p!=-1;tmp=p,p=InitData[p].parent) { if(InitData[p].lchild==tmp) dc[start++]='0'; else dc[start++]='1'; } dc[start]='\0'; HC[m]=new char[length]; start=0; strcpy(HC[m],dc); } delete [] dc; } #endif
测试代码:
#include"huff.h" #include<iostream> using namespace std; int main() { int i=0; huffmanTree ht=new huffmanNode[8]; for(;i<8;++i) { ht[i].val='a'+i; } ht[0].weight=5; ht[1].weight=29; ht[2].weight=7; ht[3].weight=8; ht[4].weight=14; ht[5].weight=23; ht[6].weight=3; ht[7].weight=11; huffmanCoding hc(ht,8); hc.Encode(8); for(int i=0;i<8;++i) cout<<hc.HC[i]<<endl; return 0; }
测试结果,即编码结果为:
大家可能发现 与书上的结果不一样,严的书是 左节点必须为叶子节点,并且强制附0,我认为这不正确。huffman的定义不是这样的,如有异议可交流。