哈夫曼树及其扩展应用.docx
- 文档编号:29622967
- 上传时间:2023-07-25
- 格式:DOCX
- 页数:54
- 大小:56.64KB
哈夫曼树及其扩展应用.docx
《哈夫曼树及其扩展应用.docx》由会员分享,可在线阅读,更多相关《哈夫曼树及其扩展应用.docx(54页珍藏版)》请在冰豆网上搜索。
哈夫曼树及其扩展应用
哈夫曼树及其扩展应用
(彭宗山0304201040004201班)
一、问题描述与分析
在数据通讯中,经常需要将传送的文字转换成由二进制字符0,1组成的二进制串,我们称之为编码。
例如,假设要传送的电文为ABACCDA,电文中只含有A,B,C,D四种字符,若这四种字符采用表1(a)所示的编码,则电文的代码为000010000100100111000,长度为21。
在传送电文时,我们总是希望传送时间尽可能短,这就要求电文代码尽可能短,显然,这种编码方案产生的电文代码不够短。
现在讨论能缩短传送电文的总长度,从而缩短传送时间呢?
表1(b)所示为另一种编码方案,用此编码对上述电文进行编码所建立的代码为00010010101100,长度为14。
在这种编码方案中,四种字符的编码均为两位,是一种等长编码。
我们应该想到,若采用不等长编码,让出现频率高的字符具有较短的编码,让出现频率低的字符具有较长的编码,这样又可能缩短传送电文的总长度。
如果在编码时考虑字符出现的频率,让出现频率高的字符采用尽可能短的编码,出现频率低的字符采用稍长的编码,构造一种不等长编码,则电文的代码就可能更短。
如当字符A,B,C,D采用表1(c)所示的编码时,上述电文的代码为0110010101110,长度仅为13。
表1字符的四种不同的编码方案
字符编码字符编码字符编码字符编码
A000A00A0A01
B010B01B110B010
C100C10C10C001
D111D11D111D10
(a)(b)(c)(d)
二、数据结构描述
哈夫曼树可用于构造使电文的编码总长最短的编码方案。
具体做法如下:
设需要编码的字符集合为{d1,d2,…,dn},它们在电文中出现的次数或频率集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶结点,w1,w2,…,wn作为它们的权值,构造一棵哈夫曼树,规定哈夫曼树中的左分支代表0,右分支代表1,则从根结点到每个叶结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,我们称之为哈夫曼编码。
在哈夫曼编码树中,树的带权路径长度的含义是各个字符的码长与其出现次数的乘积之和,也就是电文的代码总长,所以采用哈夫曼树构造的编码是一种能使电文代码总长最短的不等长编码。
在建立不等长编码时,必须使任何一个字符的编码都不是另一个字符编码的前缀,这样才能保证译码的唯一性。
例如表1(d)的编码方案,字符A的编码01是字符B的编码010的前缀部分,这样对于代码串0101001,既是AAC的代码,也是ABD和BDA的代码,因此,这样的编码不能保证译码的唯一性,我们称之为具有二义性的译码。
然而,采用哈夫曼树进行编码,则不会产生上述二义性问题。
因为,在哈夫曼树中,每个字符结点都是叶结点,它们不可能在根结点到其它字符结点的路径上,所以一个字符的哈夫曼编码不可能是另一个字符的哈夫曼编码的前缀,从而保证了译码的非二义性。
为了不等长编码不是前缀编码,可用该字符集中的每个字符作为叶子结点生成一棵编码二叉树,为了获得传送电文的最短长度,可将每个字符出现频率作为字符结点的权值赋予给结点上,求出此树的最小带权路径长度就等于求出了传送电文的最短长度。
因此,求传送电文的最短长度问题就转化为求由字符集中的所有字符作为叶子结点,由字符的出现频率作为其权值所产生的哈夫曼树的问题。
例如,假定电文中只使用A、B、C、D、E、F这六种字符,若进行等长编码,它们分别需要3位二进制字符,可依次编码为000、001、010、011、100、101。
由常识可知,电文中的每个字符的出现频率一般是不同的。
假定在一份电文中,这6个字符的出现频率分别为4、2、6、8、3、2,则电文被编码后的总长度L为:
L=3×(4+2+6+8+3+2)=75
图1编码哈夫曼树
根据前面所讨论的例子,生成的编码哈夫曼树如图1所示。
由编码哈夫曼树得到的字符编码称作哈夫曼编码。
在图6中,A、B、C、D、E、F这6个字符的哈夫曼编码分别是:
00、1110、01、10、110、1111。
电文的最短传送长度为:
L=WPL=4×2+2×4+6×2+8×2+3×3+2×4=61
显然,这要比等长编码所得到的传送总长度75要小的多。
三、算法分析
1.实现哈夫曼编码的算法可分为两大部分:
(1)构造哈夫曼树;
(2)在哈夫曼树上求叶结点的编码。
求哈夫曼编码,实质上就是在已建立的哈夫曼树中,从叶结点开始,沿结点的双亲链域回退到根结点,每回退一步,就走过了哈夫曼树的一个分支,从而得到一位哈夫曼码值,由于一个字符的哈夫曼编码是从根结点到相应叶结点所经过的路径上各分支所组成的0,1序列,因此先得到的分支代码为所求编码的低位码,后得到的分支代码为所求编码的高位码。
我们可以设置一数据结构用来存放各字符的哈夫曼编码信息,数据结构如下:
structCode
{
intbit[MaxBit];//存储该字符的哈夫曼编码
intstart;//记录编码的起始位置
intweight;//权值
charcharacter;//字符
};
2.实现哈夫曼编码的算法也可分为两大部分:
(1)读取建好的哈夫曼树;
(2)根据哈夫曼树求叶结点。
解码时,从根节点出发,遇到0就走到左孩子,遇到1就走到右孩子,直至叶节点,然后读取该叶节点。
接着再从根节点出发,接着解码。
四、推广的哈夫曼编码
1.问题描述
当给出一个字符串,然后用0,1,2进行编码,满足以下条件:
(1)编码是唯一的,即不同的字符有不同的编码;
(2)使总的编码长度为最短。
2.算法分析
假设有字符串:
‘abcaabcdeffedfaaxyytfb’
统计结果为:
a
f
b
c
d
e
y
x
t
5
4
3
2
2
2
2
1
1
归并时,首先归并出现频率最少的3个字符,本题例子中,先归并t,x,y三个字符,然后重新排序,如下表:
a
f
(y,x,t)
b
c
d
e
5
4
4
3
2
2
2
第二次归并c,d,e,在重新排序:
(c,d,e)
a
f
(y,x,t)
b
6
5
4
4
3
以此类推,最后归并成一棵树:
得到编码:
c:
00,d:
01,e:
02,a:
1,f:
20,b:
22,y:
210,x:
211,t:
212
在编码得过程中,如果字符数不满足N=3+2K(K=0,1,2,3,…),可以添加一个出现频率为0的任意字符。
例如,每一个字符出现的频率数如下表:
a
b
c
d
e
f
g
h
i
j
8
7
8
16
2
7
4
2
1
3
构造哈夫曼表
频率数
左
中
右
字符
1
13
15
a
2
7
14
b
3
8
15
c
4
16
16
d
5
2
13
e
6
7
14
f
7
4
14
g
8
2
12
h
9
1
12
i
10
3
13
j
11
0
12
w
12
3
13
11
9
8
13
8
15
5
10
12
14
18
16
2
6
7
15
29
16
1
3
13
16
55
4
14
15
其中W是补足字符,频率为0。
五、使用说明
本程序有以下几个与用户交流的界面,在此将一一阐述其功能及使用。
运行程序后,将出现下面主菜单界面
界面如下:
选择1,进入哈夫曼树编码主菜单界面:
选择1进行编码,选择2进行解码。
在使用本菜单前建议先选择3查看说明。
选择4返回主菜单。
在主菜单里选择2,进入哈夫曼改进树编码主菜单界面:
选择1进行编码,选择2进行解码。
在使用本菜单前建议先选择3查看说明。
选择4返回主菜单。
六、调试分析说明
1.算法上的难题
在编码时,最大的问题就是在建树过程中如何用堆排序法选择权值最小的两个节点。
因为在建堆时,每选择两个节点建立新节点后,堆中数据就变了,并且数据的下标不是按连续排列的。
为解决这一问题,我设了一个数组,把没有选择过的节点全存进来,然后进行堆排序。
为解决下标问题,我在定义结构体节点时加了存储下标的量,在树的初始化时就把节点自身在树中的位置存起来了。
在解码时,遇到的最大的问题就是如何根据树的结构进行解码。
关键是怎样把树的结构读到解码函数中来,为此,我在编码时就把树的结构存进了文件,在解码时在此文件中读取树的结构。
在哈夫曼改进树编码过程中,会出现这样的问题,字符种类个数不能正好建成一个双亲带三个孩子的树。
为此要判断字符种类满不满足如下关系:
N=3+2K(K=0,1,2,3,…);如果不满足,则在建树时要增加一个权值为0的节点,字符种类也要加1。
2.调试过程中的问题
在调试程序的过程中,首先对一些语法和编译错误调试,使变量名与相应的程序段对应,由于该程序相对来讲稍有些长,前后有些变量不容易联系起来,但是在错误信息的提示下一般还是很容易找到。
编译、连接的成功并不意味着程序的最终成功。
逻辑上的错误机器不易检查出来,这时需要对数据结果进行分析。
这种错误的查找是最难的,需要编程序的人有相当的耐心和细心去把问题找出来。
这也是本次程序编辑过程中碰到的最大的难题。
往往运行之后得不到另人满意的结果,此时最好的解决方法就是分布执行了。
若在程序中找不到问题,则再来考虑算法是否逻辑严谨,再进行修改。
如此循环往复,直到最后程序运行成功。
在本次程序编辑过程中,我们就是常遇到编译能通过,能够运行程序,但是编码就是不正确,我用分步执行的方法一步步的调试,最终调了出来。
七、特色展示
本程序有三大特色:
(1)与用户的交互性好,有各种输入输出提示;
(2)程序稳定性能好,可以解决用户的错误输入
(3)本程序增加了哈夫曼改进树编码,减小了平均码长。
八、总结
这次数据结构课程设计使我对此门课充满了乐趣,也学到了很多知识,数据结构在计算机专业中的地位是非常重要的。
它是一门重要的基础学科,为以后对计算机的学习奠定了基础。
通过此次课程设计,加深了我对数据结构理解,提高了我的编程能力。
同时也提高了我吃苦耐劳的能力。
在需要细心和耐心的程序调试过程中,养成了我良好的编程风格,培养了我解决问题的能力
在以后的生活和学习过程中,我一定会发扬这种精神的!
附:
源程序
#HaffIn.h中的内容#
#include
voidHaffIn()
{
system("cls");
cout< cout<<"*说明*"< cout<<"①原文章存储在文件Text.txt中,编码后的文章存储在文件Text1.txt中,各个"< cout<<"字符对应的哈夫曼码存储在文件Code.txt中。 "< cout<<"②解码后的文章存储在文件Text2.txt中。 "< cout<<"③为了使解码后的文章能和原文作比较,验证编码和解码的正确性,原文没有"< cout<<"被删除。 "< cout<<"④在解码时请确保已经编过码。 "< system("pause"); system("cls"); } #Haffman.h中的内容# #include #include #include #defineMaxBit128 //************************************ //哈夫曼树的节点结构 //************************************ structHaffNode { intweight;//权值 intflag;//用来判断该节点是否为叶子节点 intparent;//双亲 intleftChild;//左孩子 intrightChild;//右孩子 intposition;//在HaffTree树中的位置 charcharacter;//字符 }; //************************************ //存放哈夫曼编码的数据元素结构 //************************************ structCode { intbit[MaxBit];//存储该字符的哈夫曼编码 intstart;//记录编码的起始位置 intweight;//权值 charcharacter;//字符 }; //************************************ //已知HaffTree[s...m]中纪录的权值除HaffTree[s].weight之外均满足堆的定义, //本函数调整HaffTree[s]的权值,使HaffTree[s...m]成为一个小顶堆 //************************************ voidHeapAdjust(HaffNodeHaffTree[],ints,intm) { intj; HaffNoderc; rc=HaffTree[s]; for(j=2*s;j<=m;j*=2)//沿权值较小的孩子节点向下筛选 { if(j { j++; } if(rc.weight<=HaffTree[j].weight)//rc应插入在位置s上 { break; } HaffTree[s]=HaffTree[j]; s=j; } HaffTree[s]=rc;//插入rc } //************************************ //对长度为m的顺序存储结构HaffTree[]进行堆从大到小排序 //************************************ voidHeapSort(HaffNodeHaffTree[],intm) { intk; HaffNodet; for(k=m/2;k>0;--k)//把线性数组HaffTree[]建成小顶堆 { HeapAdjust(HaffTree,k,m); } for(k=m;k>1;--k)//将小顶堆从大到小排序 { t=HaffTree[1];//将堆顶记录和当前未经排序子序列HaffTree[1...k]中最后一个记录相互交换 HaffTree[1]=HaffTree[k]; HaffTree[k]=t; HeapAdjust(HaffTree,1,k-1);//将HaffTree[1...k-1]重新调整为小顶堆 } } //************************************ //建立叶结点个数为n权值为weight的哈夫曼树HaffTree //************************************ voidHaffmanTree(charch[],intweight[],intn,HaffNodeHaffTree[]) { intj,k,m,x1,x2; HaffNodetree[255]; //哈夫曼树HaffTree初始化 for(inti=0;i<2*n-1;i++) { if(i { HaffTree[i].weight=weight[i]; HaffTree[i].character=ch[i]; } else { HaffTree[i].weight=0; HaffTree[i].character='@'; } HaffTree[i].flag=0; HaffTree[i].parent=-1; HaffTree[i].leftChild=-1; HaffTree[i].rightChild=-1; HaffTree[i].position=i; } //构造哈夫曼树HaffTree的n-1个非叶结点 for(i=0;i { //利用堆排序法选出权值最小的两个节点,位置分别为x1、x2 k=1; for(j=0;j { if(HaffTree[j].flag==0) { tree[k]=HaffTree[j]; k++; } } m=k-1;//m记下未合并子树的个数 HeapSort(tree,m);//对tree[]进行堆排序 x1=tree[m].position;//权值最小的节点的位置 x2=tree[m-1].position;//权值次小的节点的位置 //将找出的两棵权值最小的子树合并为一棵子树 HaffTree[x1].parent=n+i; HaffTree[x2].parent=n+i; HaffTree[x1].flag=1; HaffTree[x2].flag=1; HaffTree[n+i].weight=HaffTree[x1].weight+HaffTree[x2].weight; HaffTree[n+i].leftChild=x1; HaffTree[n+i].rightChild=x2; } } //************************************ //由n个节点的哈夫曼树HaffTree构造哈夫曼编码HaffCode //************************************ voidHaffmanCode(HaffNodeHaffTree[],intn,CodeHaffCode[]) { Code*cd=newCode; intchild,parent; //求n个节点的哈夫曼编码 for(inti=0;i { cd->start=n-1; cd->weight=HaffTree[i].weight; child=i; parent=HaffTree[child].parent; //由叶子节点向上直到根节点 while(parent! =-1) { if(HaffTree[parent].leftChild==child) { cd->bit[cd->start]=0; } else { cd->bit[cd->start]=1; } cd->start--; child=parent; parent=HaffTree[child].parent; } //保存叶节点的编码和不等长编码的起始位置 for(intj=cd->start+1;j { HaffCode[i].bit[j]=cd->bit[j]; } HaffCode[i].start=cd->start; HaffCode[i].weight=cd->weight; HaffCode[i].character=HaffTree[i].character; } } #ImHaffmanMenu.h中的内容# #include #include #include"HaffIn.h" #include"Menu.h" voidHaffmanMenu() { intchoise; intflag=1; while(flag) { DisplayMenu();//输出菜单 cin>>choise; switch(choise) { case1: EnCode();break;//建立哈夫曼树,并对文章进行编码 case2: DeCode();break;//对编码后的文章进行解码 case3: HaffIn();break;//说明 case4: flag=0;system("cls");break;//返回主菜单 default: system("cls"); cout< cout<<""; cout<<"输入错误! 请重新输入! "< system("pause"); system("cls"); } } } #ImHaffIn.h中的内容# #include voidImHaffIn() { system("cls"); cout< cout<<"*说明*"< cout<<"①原文章存储在文件Text.txt中,编码后的文章存储在文件ImText1.txt中,各个"< cout<<"字符对应的哈夫曼码存储在文件ImCode.txt中。 "< cout<<"②解码后的文章存储在文件ImText2.txt中。 "< cout<<"③为了使解码后的文章能和原文作比较,验证编码和解码的正确性,原文没有"< cout<<"被删除。 "< cout<<"④在解码时请确保已经编过码。 "< system("pause"); system("cls"); } #ImHaffman.h中的内容# #include #include
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 哈夫曼树 及其 扩展 应用