霍夫曼编码.docx
《霍夫曼编码.docx》由会员分享,可在线阅读,更多相关《霍夫曼编码.docx(18页珍藏版)》请在冰点文库上搜索。
霍夫曼编码
霍夫曼编码
四川大学计算机学院2009级戚辅光
【关键字】
霍夫曼编码原理霍夫曼译码原理霍夫曼树霍夫曼编码源代码霍夫曼编码分析霍夫曼编码的优化霍夫曼编码的应用
【摘要】
哈夫曼编码(HuffmanCoding)是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。
uffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫作Huffman编码。
哈夫曼压缩是个无损的压缩算法,一般用来压缩文本和程序文件。
它属于可变代码长度算法一族。
意思是个体符号(例如,文本文件中的字符)用一个特定长度的位序列替代。
因此,在文件中出现频率高的符号,使用短的位序列,而那些很少出现的符号,则用较长的位序列。
【正文】
引言
哈夫曼编码(HuffmanCoding)是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。
uffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫作Huffman编码。
霍夫曼编码原理:
霍夫曼编码的基本思想:
输入一个待编码的串,首先统计串中各字符出现的次数,称之为频次,假设统计频次的数组为count[],则霍夫曼编码每次找出count数组中的值最小的两个分别作为左右孩子,建立他们的父节点,循环这个操作2*n-1-n(n是不同的字符数)次,这样就把霍夫曼树建好了。
建树的过程需要注意,首先把count数组里面的n个值初始化为霍夫曼树的n个叶子节点,他们的孩子节点的标号初始化为-1,父节点初始化为他本身的标号。
接下来是编码,每次从霍夫曼树的叶子节点出发,依次向上找,假设当前的节点标号是i,那么他的父节点必然是myHuffmantree[i].parent,如果i是myHuffmantree[i].parent的左节点,则该节点的路径为0,如果是右节点,则该节点的路径为1。
当向上找到一个节点,他的父节点标号就是他本身,就停止(说明该节点已经是根节点)。
还有一个需要注意的地方:
在查找当前权值最小的两个节点时,那些父节点不是他本身的节点不能考虑进去,因为这些节点已经被处理过了。
霍夫曼树:
下面是字符串agdfaghdabsb的霍夫曼编码的霍夫曼树:
字符串:
agdfaghdabsb
出现的字符
字符出现的次数
a
3
g
2
d
2
f
1
h
1
b
2
s
1
合计
12
由上面的霍夫曼树可知各个字符的编码如下:
a:
01
b:
010
d:
011
f:
100
g:
101
h:
110
s:
111
所以整个串的编码为:
011010111000110111001101010111010
霍夫曼译码原理:
对于霍夫曼的译码,可以肯定的是其译码结果是唯一的。
证明:
因为霍夫曼编码是根据霍夫曼树来确定的,霍夫曼树是一棵二叉树,编码的时候是从树根一直往下走,直到走到叶子节点为止,在其经过的路径上,如果是树的左子树则为0,否则为1。
因为每一次都要走到树的叶子节点,多以不可能存在两个编码a和b,使得a是b的前缀或者b是a的前缀。
所以编码一定可以唯一确定。
根据上面的结论,我们可以很清楚地直到译码的方法:
定义两个指针p1,p2,P1指向当前编码的开始位置,P2指向当前编码的位置,如果P1-P2这一段编码能在编码库里面找到完全对应的编码结果,则译码成功,该段编码的译码结果就是与编码库里完全对应的编码的字符。
循环次操作,直到译码结束!
例一:
假设有一段字符含有a,,c,d三个字符,经过编码之后三个字符对应的编码结果分别为:
a:
01
c:
010
d:
011
现在给你一段编码0110101,要求将其译码!
按照上面介绍的方法我们可以直到:
编码的前三个字符是且仅是d的编码,所以011译码为d,依次译码可得整串的译码结果为daa
霍夫曼编码源代码:
#include
#include
#include
#include
usingnamespacestd;
#defineINF0x7fffffff//无穷大
structHuffmantree//霍夫曼树的节点
{
intweight;
intparent,ld,rd;
};
structmyNode
{
charch;
intnum;
};
structmycode//字符和其对应的编码
{
charch;//字符
ints[50];//ch的编码
intlen;//编码长度
};
intnNode;//叶子节点数目
inttotalNode;//霍夫曼树的总节点个数
chartoCode[100000];//待编码的字符串
myNodemyToCode[100000];//待编码的字符串和权值
intweightOfToCode[100000];//字符串的权值!
HuffmantreemyHuffmantree[1000000];//霍夫曼树(数组模拟)
charallchar[1000000];//所哟出现过的字符
mycodecoder[1000000];//字符与对应的编码
intLen;//待编码的字符的总长度
intCoding[100000];//译码之后的01串
intlenOfCoding;//01串的长度
voidbuild(intn);//建立霍夫曼树
voidselect(int&a,int&b);//选择两个权值最小的节点
voidCode();//编码
voidprintCode();//打印编码
voiddeCode();//译码
intmatch(intl,inth);
intmain()
{
inti;
cout<<"\n=============================霍夫曼编码程序=============================";
cout<<"\n作者:
戚辅光"<cout<<"时间:
2010-11-23"<while
(1)
{
printf("请输入待编码的字符串\n");
intflag=0;
gets(toCode);
intlen=strlen(toCode);
if(len==1)
{
flag=1;
}
Len=len;
mapmyMap;
for(i=0;i{
myMap[toCode[i]]++;
}
map:
:
iteratoriter;
inth=1;
for(iter=myMap.begin();iter!
=myMap.end();iter++)
{
myToCode[h].ch=iter->first;
allchar[h]=iter->first;
weightOfToCode[h]=iter->second;
myToCode[h++].num=iter->second;
}
nNode=h-1;//叶子节点个数
cout<<"----------------------字符统计如下--------------------------------------"<cout<<"字符次数"<for(i=1;i{
cout<<""<}
cout<totalNode=nNode;//totalNode初始值为nNode
cout<<"-----------霍夫曼树节点信息如下(子节点为-1表示是叶子节点)---------------"<build(nNode);
cout<Code();
cout<<"\n-------------------------字符串的编码结果如下--------------------------\n";
printCode();
cout<<"\n-------------------------01串的译码结果如下-----------------------------\n";
deCode();
cout<<"\n是否继续?
(Y/N)\n";
charcon[10];
cin>>con;
charfang=toupper(con[0]);
if(fang=='N')
{
break;
}
getchar();
}
return0;
}
voidbuild(intn)//建立霍夫曼树
{
inti;
intm=2*n-1;//n个叶子节点的霍夫曼树总节点数为2*n-1
for(i=1;i<=n;i++)//初始化霍夫曼数组
{
myHuffmantree[i].weight=weightOfToCode[i];//叶子节点权值为字符出现次数
myHuffmantree[i].ld=-1;//叶子节点没有左孩子
myHuffmantree[i].rd=-1;//叶子节点没有右孩子
myHuffmantree[i].parent=i;//叶子节点父节点先初始化为他本身
}
for(i=n+1;i<=m;i++)
{
inta,b;
select(a,b);
myHuffmantree[a].parent=i;
myHuffmantree[b].parent=i;
myHuffmantree[i].ld=a;
myHuffmantree[i].rd=b;
myHuffmantree[i].weight=myHuffmantree[a].weight+myHuffmantree[b].weight;
myHuffmantree[i].parent=i;
}
for(i=1;i<=totalNode;i++)
{
printf("节点:
%3d权值:
%3d左节点:
%3d右节点:
%3d父节点:
%3d\n",i,myHuffmantree[i].weight,myHuffmantree[i].ld,myHuffmantree[i].rd,myHuffmantree[i].parent);
}
}
voidCode()//编码
{
inti,j;
intnumOfCode[100000];
cout<<"--------------------------各字符编码结果如下----------------------------"<if(Len==1)
{
cout<"<<"0\n";
return;
}
for(i=1;i<=nNode;i++)
{
j=i;
inth=0;
while(myHuffmantree[j].parent!
=j)
{
intx=j;
j=myHuffmantree[j].parent;
if(myHuffmantree[j].ld==x)
{
numOfCode[h++]=0;
}
elseif(myHuffmantree[j].rd==x)
{
numOfCode[h++]=1;
}
}
cout<<""<";
intx=0;
coder[i].len=h;
coder[i].ch=allchar[i];
for(intk=h-1;k>=0;k--)
{
coder[i].s[x++]=numOfCode[k];
printf("%d",numOfCode[k]);
}
cout<}
}
voidselect(int&a,int&b)//选择两个权值最小的节点
{
inti;
intmin1=INF;
intmin2=INF;
intsign1=1;//最小值的下标
intsign2=2;//次小值的下标
for(i=1;i<=totalNode;i++)
{
if(myHuffmantree[i].parent==i)//说明其是已经更新过的节点
{
if(myHuffmantree[i].weight{
min1=myHuffmantree[i].weight;
sign1=i;
}
}
}
for(i=1;i<=totalNode;i++)
{
if(myHuffmantree[i].parent==i)//说明其是已经更新过的节点
{
if(myHuffmantree[i].weight=sign1)
{
min2=myHuffmantree[i].weight;
sign2=i;
}
}
}
a=sign1;
b=sign2;
totalNode++;//总节点数加1
}
voidprintCode()//打印编码结果!
{
inti;
if(Len==1)//长度为1的时候特殊考虑
{
cout<<"0\n";
return;
}
inth=0;
for(i=0;i{
for(intj=1;j<=nNode;j++)
{
if(toCode[i]==coder[j].ch)
{
for(intk=0;k{
printf("%d",coder[j].s[k]);
Coding[h++]=coder[j].s[k];
}
}
}
}
lenOfCoding=h;
cout<}
voiddeCode()//译码
{
inti,j;
intbegin=0;
for(i=0;i{
if(match(begin,i)!
=-1)
{
intx=match(begin,i);
printf("%c",coder[x].ch);
begin=i+1;
}
}
printf("\n");
}
intmatch(intl,inth)//译码的辅助函数(寻找匹配)
{
inti,j,k;
intflag=0;
for(i=1;i<=nNode;i++)
{
if(coder[i].len!
=h-l+1)
{
continue;
}
k=l;
for(j=0;j{
if(Coding[k]!
=coder[i].s[j])
{
break;
}
if(j==coder[i].len-1)
{
flag=1;
gotook;
}
k++;
}
}
ok:
;
if(flag==1)//查找成功返回对应的下表
{
returni;
}
else//查找不成功返回-1;
{
return-1;
}
}
霍夫曼编码代码分析:
1.运行环境:
win7操作系统,Inter(R)Core(TM)Duot6600@2.20GHz2.20GHz
内存:
2.00GB,32位操作系统
2.程序运行结果:
3.程序功能模块分析:
整个程序包含6个函数:
A.voidbuild(intn);
B.voidselect(int&a,int&b);
C.voidCode();
D.voidprintCode();
E.voiddeCode();
F.intmatch(intl,inth);
其中A是用来建立霍夫曼树,B是在A中调用的一个辅助函数,用来查找两个权值最小的节点的下标,C是对字符串编码,D是现实编码结果,E是译码01串,F是E的辅助函数,用来寻找编码的匹配。
包含3个结构体:
A.structHuffmantree//霍夫曼树的节点
{
intweight;
intparent,ld,rd;
};
B.structmyNode//字符和其权值
{
charch;//字符
intnum;//权值
};
C.structmycode//字符和其对应的编码
{
charch;//字符
ints[50];//ch的编码
intlen;//编码长度
};
各结构功能如注释!
4.程序效率分析:
该程序几乎都是用“暴力”的手段完成编码与译码的,所以当要编码的字符比较长,达到10^8长度时运行就会很慢,主要原因在于在建树时每次选择权值最小的两个是用暴力枚举的,还有译码时每次都要在编码库里面去一一匹配,假设编码之后的01串长度为10^7,而其中出现的不同字符数为200,在最坏情况下,译码的时候就需要200*10^7的时间,在这种情况下程序运行就会比较慢,当然,这只是最坏估计,实际上是不可能达到这么高的时间复杂度的,因为不可能每次寻找编码匹配时都要遍历到最后才找到!
5.程序的优化:
既然用“暴力”的方法有时效率会很低,那么有没有什么优化方法呢?
答案是肯定的!
在建树的时候可以用一个优先队列将个节点信息存在队列里面,优先队列按照节点的权值从小到大排序,所以每次只需取出队列的前两个节点就是权值最小的两个节点,而不需遍历所有节点来选择最小的。
优先队列里面是右堆来排序的,时间复杂度很低,为lg(n),所以用有限队列能大幅度降低程序建树的时间复杂度!
霍夫曼编码的应用:
随着信息技术的飞速发展,各种各样的信息需要传输,传输信息就要得先经过编码,然后再译码,可见编码技术的提高对整个信息产业有着举足轻重的作用。
霍夫曼编码是一种可变的无损压缩方法,其效率也比较高,所以在当今网络传输中意义重大。
霍夫曼树是一棵最有二叉树,在各种程序设计中都用到它来降低程序运行的时间复杂度。