找回密码
 立即注册
查看: 212|回复: 0

哈夫曼编码/译码器

[复制链接]
发表于 2023-2-18 16:02 | 显示全部楼层 |阅读模式
文章目录

    一、设计任务及要求二、设计导向
      1. 设计目的2. 设计课题3. 总体设计方案4. 详细设计5. 系统测试与结果分析
    三、课程设计总结四、 附录:源程序清单



一、设计任务及要求

哈夫曼编码是一种最优变长码,即带权路径最小。这种编码有很强的应用背景,是数据压缩中的一个重要理论依据。对输入的一串文字符号实现哈夫曼编码,再对哈夫曼编码生成的代码串进行译码,输出电文字符串。要求完成以下功能:
    针对给定的字符串,建立哈夫曼树。生成哈夫曼编码。对编码文件译码。

二、设计导向

1. 设计目的

(1) 巩固和加深对数据结构课程所学知识的理解,了解并掌握数据结构与算法的设计方法;
(2) 初步掌握软件开发过程的问题分析、系统设计、程序编码、测试等基本方法和技能;
(3) 提高综合运用所学的理论知识和方法,独立分析和解决问题的能力;
(4) 训练用系统的观点和软件开发一般规范进行软件开发,培养软件工作者所应具备的科学的工作方法和作风;
(5) 培养查阅资料,独立思考问题的能力。
2. 设计课题

哈夫曼编译码器的主要功能是先建立哈夫曼树,然后利用建好的哈夫曼树生成哈夫曼编码后进行译码。在数据通信中,通常需要将传送文字转换成由二进制字符0,1组成的二进制串,称之为编码。构建一个哈夫曼树,设定哈夫曼树中的左分支为0,右分支代表1,则从根结点到每个叶子节点所经过的路径组成的0和1的序列便为该节点对应字符的编码,称之为哈夫曼编码。最简单的二进制编码方式是等长编码。若采用不等长编码,让出现频率高的字符具有较短的编码,让出现频率低的字符具有较长的编码,这样可以有效缩短传送文字的总长度。哈夫曼树则是用于构造使编码总长最短,最节省空间成本的编码方案。
设计包含几个方面:
(1)哈夫曼树的建立
哈夫曼树的建立由哈夫曼算法的定义可知,初始森林中共有n课只含有根节点的二叉树,算法的第二步是:当前森林中两颗根节点权值最小的二叉树,合并为一个新的二叉树,每合并一次,森林中就减少一棵树,产生一个新节点,要进行n-1次的合并,共产生n-1个新节点,他们都是具有两个孩子的分支节点。由此可知,最终得到的哈夫曼树中一共有2n-1个节点,其中n个节点是由初始森林的n个孤立节点,哈夫曼树中没有度数为1的分支节点,我们可以利用一个大小为2n-1的一维数组来存储哈夫曼树中的节点。
(2)哈夫曼编码
首先定义哈夫曼编码类型,根据实际需要定义类型如下:
  1. Typedef struct//结点的结构{unsignedint weight;unsignedint parent,lchild,rchild;}HTNode,*HuffmanTree;//动态分配数组存储哈夫曼树
  2. Typedef char**HuffmanCode;//动态分配数组存储哈夫曼树
复制代码
(3)代码文件的译码
译码的思路是:读取文件中的编码,并与原先生成的哈夫曼编码表进行比较,遇到相等时,取出相应字符存入一个新字符串中。
3. 总体设计方案

设计哈夫曼编码译码器的总体思路:
(1)首先建立哈夫曼树,将哈夫曼树初始化,输入权值及字符建立哈夫曼树。
(2)输出哈夫曼的被编码的字符及对应的编码。
(3)输入要译码的哈夫曼编码字符串进行译码。



4. 详细设计

(1) 建树模块
在建立哈夫曼树时需要录入字符及其权值。
(2) 设计思路
在建立哈夫曼树时输入的字符及权值的个数不确定,因此采用了动态分配存储的方法。因为在后面要输出编码的字符及对应的哈夫曼编码,要统计输入了几个字符所以输入字符函数有一个返回值,返回所输入字符的个数。并且在选最初的两个结点时,要找两个权值最小的两个,则该两个结点从原来结点中删除,在此用了给它们赋最大权值。
(3) 流程图



(4) 编码模块
① 设计思路
在已建立的哈夫曼的基础上,逆向求哈夫曼编码。利用动态分配存储的方式,因为是逆向存储,所以也应逆向存储哈夫曼编码。
② 流程图


(5) 译码模块
① 设计思路
动态分配存储输入的编码,遍历哈夫曼树,若编码等于0,向左遍历其左子树,并判断其左右孩子是否为零,若为都零则输出其data域中的字符并存储。并判断输入的编 码是否读到‘\0’ ,是也退出循环,并判断退出时结点的左右孩子是否都为零,是则为完全译码否就为不完全译码。
② 流程图



5. 系统测试与结果分析







三、课程设计总结

    这次课程设计的心得体会通过实践我的收获如下:
    ① 巩固和加深了对数据结构的理解,提高综合运用本课程所学知识的能力。
    ② 培养了我选用参考书,查阅手册及文献资料的能力。培养独立思考,深入研究,分析问题、解决问题的能力。
    ③ 通过实际编译系统的分析设计、编程调试,掌握应用软件的分析方法和工程设计方法。
    ④ 通过课程设计,培养了我严肃认真的工作作风,逐步建立正确的生产观念、经济观念和全局观念。
    根据我在实习中遇到得问题,我将在以后的学习过程中注意以下几点:
    ① 认真上好专业实验课,多在实践中锻炼自己。
    ② 写程序的过程中要考虑周到,严密。
    ③ 在做设计的时候要有信心,有耐心,切勿浮躁。
    ④ 认真的学习课本知识,掌握课本中的知识点,并在此基础上学会灵活运用。
    ⑤ 在课余时间里多写程序,熟练掌握在调试程序的过程中所遇到的常见错误,以便能节省调试程序的时间。
通过这次课程设计,让我对一个程序的数据结构有更全面更进一步的认识,根据不同的需求,采用不同的数据存储方式,不一定要用栈,二叉树等高级类型有时用基本的一维数组,只要运用得当,也能达到相同的效果,甚至更佳,就如这次的课程设计,通过用for的多重循环,舍弃多余的循环,提高了程序的运行效率。在编写这个程序的过程中,我复习了之前学的基本语法,哈弗曼树最小路径的求取,哈弗曼编码及译码的应用范围,程序结构算法等一系列的问题它使我对数据结构改变了看法。在这次设计过程中,体现出自己单独设计模具的能力以及综合运用知识的能力,体会了学以致用、突出自己劳动成果的喜悦心情,也从中发现自己平时学习的不足和薄弱环节,从而加以弥补。
通过本次数据结构的课设计,我学习了很多在上课没懂的知识,并对求哈夫曼树及哈夫曼编码/译码的算法有了更加深刻的了解,更巩固了课堂中学习有关于哈夫曼编码的知识,真正学会一种算法了。当求解一个算法时,不是拿到问题就不加思索地做,而是首先要先对它有个大概的了解,接着再详细地分析每一步怎么做,无论自己以前是否有处理过相似的问题,只要按照以上的步骤,必会顺利地做出来。
这次课程设计,我在编辑中犯了不应有的错误,设计统计字符和合并时忘记应该怎样保存数据,对文件的操作也很生疏。在不断分析后明确并改正了错误和疏漏,我的程序有了更高的质量。

四、 附录:源程序清单

  1. #include<iostream>#include<string.h>#defineMAX_MA1000#defineMAX_ZF100
  2. using namespace std;//哈夫曼树的顺序储存表示typedefstruct{int weight;//结点的权值int parent,lchild,rchild;//结点的双亲,左孩子,右孩子的下标} HTNode,*HuffmanTree;//动态分配数组来储存哈夫曼树的结点//哈夫曼编码表的储存表示typedefchar**HuffmanCode;//动态分配数组来储存哈夫曼编码//在哈夫曼树HT中选择两个双亲域为0且权值最小的两个结点,并返回它们在HT中的序号s1和s2voidSelect(HuffmanTree HT,int len,int&s1,int&s2){//len代表HT数组的长度int i,min1=0x3f3f3f3f,min2=0x3f3f3f3f;//先赋予最大值for(i=1; i<=len; i++){if(HT[i].weight<min1 && HT[i].parent==0){
  3.             min1=HT[i].weight;
  4.             s1=i;}}int temp=HT[s1].weight;//将原值存放起来,然后先赋予最大值,防止s1被重复选择
  5.     HT[s1].weight=0x3f3f3f3f;for(i=1; i<=len; i++){if(HT[i].weight<min2 && HT[i].parent==0){
  6.             min2=HT[i].weight;
  7.             s2=i;}}
  8.     HT[s1].weight=temp;//恢复原来的值}//构造哈夫曼树HTvoidCreatHuffmanTree(HuffmanTree &HT,int n){int s1,s2;if(n<=1)return;int m=2*n-1;//当n大于1时,n个叶子结点需要2*n-1个结点
  9.     HT=new HTNode[m+1];//0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点//将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0for(int i=1; i<=m;++i){
  10.         HT[i].parent=0;
  11.         HT[i].lchild=0;
  12.         HT[i].rchild=0;}
  13.     cout<<"请输入叶子结点的权值:";//输入前n个单元中叶子结点的权值for(int i=1; i<=n;++i){
  14.         cin>>HT[i].weight;}//通过n-1次的选择、删除、合并来创建哈夫曼树for(int i=n+1; i<=m;++i){//在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2Select(HT,i-1,s1,s2);//得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
  15.         HT[s1].parent=i;
  16.         HT[s2].parent=i;//s1,s2分别作为i的左右孩子
  17.         HT[i].lchild=s1;
  18.         HT[i].rchild=s2;//i的权值为左右孩子权值之和
  19.         HT[i].weight=HT[s1].weight+HT[s2].weight;}}//哈夫曼编码voidHuffmanCoding(HuffmanTree HT,HuffmanCode &HC,int n){//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
  20.     HC=new char*[n+1];//分配n个字符编码的编码表空间(头指针矢量)char*cd=new char[n];//分配临时存放编码的动态数组空间
  21.     cd[n-1]='\0';//编码结束符//从叶子开始逐个字符求哈夫曼编码for(int i=1; i<=n;++i){int start=n-1;//start开始时指向最后,即编码结束符位置int c=i;int f=HT[i].parent;//f是指向结点c的双亲结点//从叶子结点开始向上回溯,直到根结点while(f!=0){--start;//回溯一次,start向前指向一个位置//判断是结点c是f的左孩子还是右孩子if(HT[f].lchild==c){
  22.                 cd[start]='0';//结点c是f的左孩子,则生成代码0}else{
  23.                 cd[start]='1';//结点c是f的右孩子,则生成代码1}
  24.             c=f;
  25.             f=HT[f].parent;//继续向上回溯}//求出第i个字符的编码
  26.         HC[i]=new char[n-start];//为第i个字符编码分配空间strcpy(HC[i],&cd[start]);//将求得的编码从临时空间cd复制到HC的当前行中}
  27.     delete cd;//释放临时空间}//哈夫曼译码voidHuffmanDecoding(HuffmanTree HT,char a[],char zf[],char b[],int n){//a[]用来传入二进制编码,b[]用来记录译出的字符//zf[]是与哈夫曼树的叶子对应的字符,n是字符个数相当于zf[]数组的长度int q=2*n-1;//q初始化为根结点的下标int k=0;//记录存储译出字符数组的下标for(int i=0; a[i]!='\0'; i++){//for循环结束条件是读入的字符是结束符//判断读入的二进制字符是0还是1if(a[i]=='0'){//读入0,把根结点(HT[q])的左孩子的下标值赋给q,//下次循环的时候把HT[q]的左孩子作为新的根结点
  28.             q=HT[q].lchild;}elseif(a[i]=='1'){//读入1,把根结点(HT[q])的右孩子的下标值赋给q,//下次循环的时候把HT[q]的右孩子作为新的根结点
  29.             q=HT[q].rchild;}//判断HT[q]是否为叶子结点if(HT[q].lchild==0&& HT[q].rchild ==0){//如果读到一个结点的左孩子和右孩子都为0,是叶子结点,//说明已经译出一个字符,该字符的下标就是找到的叶子结点的下标
  30.             b[k++]=zf[q];//把下标为q的字符赋给字符数组b[]
  31.             q=2*n-1;//初始化q为根结点的下标}//继续译下一个字符的时候从哈夫曼树的根结点开始}//译码完成之后,用来记录译出字符的数组由于没有结束符输出的时候会报错,//所以紧接着把一个结束符加到数组最后
  32.     b[k]='\0';}voidmenu(){int n;//记录要编码的字符个数char a[MAX_MA];//储存输入的二进制字符char b[MAX_ZF];//存储译出的字符char zf[MAX_ZF];//储存要编码的字符
  33.     HuffmanTree HT=NULL;//初始化树为空树
  34.     HuffmanCode HC=NULL;//初始化编码表为空表
  35.         cout<<" =============================================================================\n";
  36.         cout<<"||                ★★★★★★★哈夫曼编码与译码★★★★★★★                ||\n";
  37.         cout<<"||============================================================================||\n";
  38.         cout<<"||============================================================================||\n";
  39.         cout<<"||                     【1】--- 创建哈夫曼树                                  ||\n";
  40.         cout<<"||                     【2】--- 进行哈夫曼编码                                ||\n";
  41.         cout<<"||                     【3】--- 进行哈夫曼译码                                ||\n";
  42.         cout<<"||                     【4】--- 退出程序                                      ||\n";
  43.         cout<<" ==============================================================================\n";
  44.         cout<<"请输入数字来选择对应的功能:";while(1){int num;if(!(cin>>num)){
  45.             cout<<"输入格式错误!请重新输入:"<<endl;}else{switch(num){case1:
  46.                     cout<<"请输入字符个数:";
  47.                     cin>>n;
  48.                     cout<<"请依次输入"<<n<<"个字符: ";for(int i=1; i<=n; i++)
  49.                         cin>>zf[i];CreatHuffmanTree(HT,n);
  50.                     cout<<endl;
  51.                     cout<<"创建哈夫曼成功!下面是该哈夫曼树的参数输出:"<<endl;
  52.                     cout<<endl;
  53.                     cout<<"结点"<<"\t"<<"字符"<<"\t"<<"权值"<<"\t"<<"双亲"<<"\t"<<"左孩子"<<"\t"<<"右孩子"<<endl;for(int i=1; i<=2*n-1; i++){
  54.                         cout<<i<<"\t"<<zf[i]<<"\t"<<HT[i].weight<<"\t"<<HT[i].parent <<"\t"<< HT[i].lchild<<"\t"<<HT[i].rchild<<endl;}
  55.                     cout<<"请输入数字来选择对应的功能:";break;case2:HuffmanCoding(HT, HC, n);
  56.                     cout<<"生成哈夫曼编码表成功!下面是该编码表的输出:"<<endl;
  57.                     cout<<endl;
  58.                     cout<<"结点"<<"\t"<<"字符"<<"\t"<<"权值"<<"\t"<<"编码"<<endl;for(int i=1; i<=n; i++){
  59.                         cout<<i<<"\t"<<zf[i]<<"\t"<<HT[i].weight<<"\t"<<HC[i]<<endl;}
  60.                     cout<<"请输入数字来选择对应的功能:";break;case3:
  61.                     cout<<"请输入想要翻译的一串二进制编码:";
  62.                     cin>>a;HuffmanDecoding(HT,a,zf,b,n);
  63.                     cout<<"译码成功!翻译结果为:"<<b<<endl;
  64.                     cout<<"请输入数字来选择对应的功能:";break;case4:
  65.                     cout<<"\n\n";
  66.                     cout<<"\n*************************************************"<<endl;
  67.                     cout<<"\n******                                     ******"<<endl;
  68.                     cout<<"\n******     谢谢使用哈夫曼编码与译码程序    ******"<<endl;
  69.                     cout<<"\n******                                     ******"<<endl;
  70.                     cout<<"\n*************************************************"<<endl;exit(0);default:
  71.                     cout<<"输入错误!没有此功能!请重新输入!"<<endl;
  72.                     cout<<endl;}}}}intmain(){menu();return0;}
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 06:00 , Processed in 0.067475 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表