《数据结构》课程设计——赫夫曼编码/译码器设计指导教师:孙树森、周维达班级:09数媒(2)班学号:E********姓名:***数据结构课程设计报告书一、实验目的1、提高分析问题、解决问题的能力,进一步巩固数据结构各种原理与方法。
2、熟悉掌握一门计算机语言,可以进行数据算法的设计。
二、实验原理1、哈夫曼树的定义:假设有n 个权值,试构造一颗有n 个叶子节点的二叉树,每个叶子带权值为wi,其中树带权路径最小的二叉树成为哈夫曼树或者最优二叉树;2、哈夫曼树的构造:weight 为输入的频率数组,把其中的值赋给依次建立的HT Node 对象中的data 属性,即每一个HT Node 对应一个输入的频率。
然后根据data 属性按从小到大顺序排序,每次从data 取出两个最小和此次小的HT Node,将他们的data 相加,构造出新的HTNode 作为他们的父节点,指针parent,leftchild,rightchild 赋相应值。
在把这个新的节点插入最小堆。
按此步骤可以构造构造出一棵哈夫曼树。
通过已经构造出的哈夫曼树,自底向上,由频率节点开始向上寻找parent,直到parent 为树的顶点为止。
这样,根据每次向上搜索后,原节点为父节点的左孩子还是右孩子,来记录1 或0,这样,每个频率都会有一个01 编码与之唯一对应,并且任何编码没有前部分是同其他完整编码一样的。
三、实验步骤先统计要压缩编码的文件中的字符字母出现的次数,按字符字母和空格出现的概率对其进行哈夫曼编码。
然后读入要编码的文件,编码后存入另一个文件;接着再调出编码后的文件,并对其进行译码输出,最后存入另一个文件中。
具体步骤:1.初始化,统计文本文件中各字符的个数作为权值,生成哈夫曼树;2.根据符号概率的大小按由大到小顺序对符号进行排序;3.把概率最小的两个符号组成一个节点;4.重复步骤2. 3 ,直到概率和为1;5.从根节点开始到相应于每个符号的“树叶”,概率大的标“0”,概率小的标“1”;6.从根节点开始,对符号进行编码;7.译码时流程逆向进行,从文件中读出哈夫曼树,并利用哈夫曼树将编码序列解码。
四、实验结果与分析哈夫曼编码是动态变长编码,临时建立概率统计表和编码树。
概率小的码比较长,概率小的码比较长。
概率大的码短,这样把一篇文件编码后,就会压缩许多。
从树的角度看,哈夫曼编码方式是尽量把短码都利用上。
首先,把一阶节点全都用上,如果码字不够时,然后,再从某个节点伸出若干枝,引出二阶节点作为码字,以此类推,显然所得码长最短,再根据建立的概率统计表合理分布和放置,使其平均码长最短就可以得到最佳码。
实验截图:五、实验心得本次实验结合了之前所学的赫夫曼算法,并利用其原理实现赫夫曼编码/译码系统;实验难度较之前的几次实验有所增加,所以花了比较多的时间来编写,测试代码。
期间参考了网上的一些程序,通过结合分析,了解其他人在实现这个算法时的思想,也借鉴了其中的一些东西,同时加入了自己的想法。
最终完成饿了本次的作业。
通过这次实验,我了解了一个算法到一个可以执行的程序之间还有很多工作需要做,深刻体会到实践出真知的到了,看似简单的算法在实现时却话费了这么多时间,但是这个过程中也让我学到了很多。
六、主要代码Huffman_Tree.h:#ifndef Huffman_Tree_h#define Huffman_Tree_h#endif#include <stdio.h>typedef struct {unsigned int weight;unsigned int parent,lchild,rchild;}HTNode, * HuffmanTree; //存储赫夫曼树的结点类型typedef char * * HuffmanCode; //用于存储字符集中各个字符相应的赫夫曼编码void strcpy(char *S1,char *S2){ //将字符串S2复制到S1int i = 0;while( S2[i] != '\0' ){S1[i] = S2[i];i++;}S1[i] = '\0';}//在HT[1]到HT[t-1]中找出权值最小的两个S1和S2----------------------------------------------- void Select(HuffmanTree HT,int t,int &s1,int &s2){int i = 1;s1 = s2 = 0;HT[0].weight = 50000;while( i <= t ){ //遍历查找权值最小的结点S1if( HT[i].parent == 0 && HT[i].weight < HT[s1].weight )s1 = i;i++;}i = 1;while( i <= t ){ //遍历查找除S1外权值最小的结点S2if( i != s1 && HT[i].parent == 0 && HT[i].weight < HT[s2].weight )s2 = i;i++;}}//根据各个字符的权值构造赫夫曼树HT,将对应的赫夫曼编码存储在HC中------------------------------------------------int HuffmanCoding( HuffmanTree &HT,HuffmanCode &HC,int *w,int n){int s1,s2,m,i,start;unsigned int c,f;HTNode * p;char *cd;if( n <= 1 ) return 0;m = 2 * n - 1; //赫夫曼树的总结点树为mHT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //申请存储赫夫曼树的空间for(p = HT + 1, i = 1; i <= n; ++i, ++p, ++w){ //将各个叶子结点的weight赋以相应的权值,parent,lchild,rchild均赋为0p->weight = *(w+1);p->parent = p->lchild = p->rchild = 0;}for( ; i <= m; ++i, ++p ){ //将各个非叶子结点的weight,parent,lchild,rchild均赋为0p->weight = p->parent = p->lchild = p->rchild = 0;}for( i = n + 1; i <= m; ++i ){ //构造赫夫曼树,给各个非叶子结点赋值Select(HT, i - 1, s1, s2);HT[s1].parent = i; HT[s2].parent = i;HT[i].lchild = s1; HT[i].rchild = s2;HT[i].weight = HT[s1].weight + HT[s2].weight;}//根据已建立的赫夫曼树对需要编码的字符进行编码----------------------------------------------------------HC = (HuffmanCode)malloc((n + 1) * sizeof(char *)); //用于存储指向存储各个字符相应赫夫曼编码的字符数组的指针cd = (char *)malloc(n * sizeof(char)); //分配工作所需空间cd[n - 1] = '\0'; //编码结束符for( i = 1; i <= n; ++i) //逐个字符求赫夫曼编码{start = n -1; //编码在数组cd[]中的最前位置//从叶子到根逆向求编码,初始值;停止条件;一次循环后对上一个节点做循环for(c = i,f = HT[i].parent; f != 0; c = f, f = HT[f].parent)if(HT[f].lchild == c)cd[ --start ] = '0';elsecd[ --start ] = '1';HC[i] = (char *)malloc((n - start)*sizeof(char)); //为第i个字符编码分配空间strcpy(HC[i], &cd[start]); //将cd[]数组的start位置到n-1位置复制给HC[i]}free(cd); //释放空间return 1;}Test.cpp:#include <stdio.h>#include <stdlib.h>#include "Huffman_Tree.h"#define Yes 1 //当程序已经调用过初始化赫夫曼树的InitHuff_T()函数,或已从htfTree文件读取过,则将Init_Mode置为Yes,否则为No#define No 0void InitHuff_T( HuffmanTree &HT, HuffmanCode &HC, char ch[],int &n ){ //初始化赫夫曼数,要求用户输入字符和相应权值int i = 1,w[100],temp,j;char a[20]; //数字转字符时,用于储存赫夫曼编码FILE *save;printf("请输入编码字符集的大小:");scanf("%d",&n); //获取用户输入的字符集个数while( i <= n ){ //获取用户输入的字符和相应权值,分别存储在ch[]和w[]数组中printf("请输入第%d个字符和该字符的权值:",i);fflush(stdin); //刷新标准输入缓冲区,把输入缓冲区里的东西丢弃scanf("%c%d",&ch[i],&w[i]);i++;}ch[i] = '\0';//哈夫曼树保存-------------------------------------------------------------------------------------------- HuffmanCoding(HT,HC,w,n); //根据用户的输入,生成赫夫曼数及各个字符相应的赫夫曼编码,分别存在HT树和HC中if(( save = fopen("hfmTree.TXT","w")) == NULL ){ //打开用于存储赫夫曼树的文件printf("Open file fail......\n");exit(0);}temp = n; //将字符集大小转换成字符形式写入到文件hfmTree.TXT中j = 0;while( temp != 0 ){//计算数字位数temp = temp / 10;j++;}temp = n; //数字转字符a[j] = '\0';while( temp != 0 ){a[j - 1] = (char)(temp % 10 + 48);temp = temp / 10;j--;}fputs(a,save);printf("%d\n",n); //向屏幕输出字符集大小nfputc('\n',save); //换行for( i = 1; i <= n; i++ ){ //分别向文件和屏幕输出各个字符和相应的赫夫曼编码fputc(ch[i],save); printf("%c\t",ch[i]); //字符fputc('\t',save);fputs(HC[i],save); printf("%s\n",HC[i]); //编码fputc('\n',save);}for(i = 1; i <= 2 * n - 1; i++ ){ //将赫夫曼树各个结点的parent,lchild,rchild分别写入到文件中temp = HT[i].parent; //将i结点的parent转换成字符并写入到文件中if(temp == 0){fputc(temp + 48,save);fputc(' ',save);}else{j = 0; //计算HT[i].parent数字位数while( temp != 0 ){temp = temp / 10;j++;}temp = HT[i].parent; //数字转字符a[j] = '\0';while( temp != 0 ){a[j - 1] = (char)(temp % 10 + 48);temp = temp / 10;j--;}fputs(a,save);fputc(' ',save);}temp = HT[i].lchild; //将i结点的lchild转换成字符并写入到文件中if(temp == 0){fputc(temp + 48,save);fputc(' ',save);}else{j = 0; //计算HT[i].lchild数字位数while( temp != 0 ){temp = temp / 10;j++;}temp = HT[i].lchild; //数字转字符a[j] = '\0';while( temp != 0 ){a[j - 1] = (char)(temp % 10 + 48);temp = temp / 10;j--;}fputs(a,save);fputc(' ',save);}temp = HT[i].rchild; //将i结点的rchild转换成字符并写入到文件中if(temp == 0){fputc(temp + 48,save);fputc('\n',save);}else{j = 0; //计算HT[i].lchild数字位数while( temp != 0 ){temp = temp / 10;j++;}temp = HT[i].rchild; //数字转字符a[j] = '\0';while( temp != 0 ){a[j - 1] = (char)(temp % 10 + 48);temp = temp / 10;j--;}fputs(a,save);fputc('\n',save);}}fclose(save);}//编码,并将所得编码存储到文件-----------------------------------void Encoding(HuffmanTree &HT, HuffmanCode &HC, char ch[]){FILE *ToBeTran,*CodeFile;int i;char c;if(( ToBeTran = fopen("ToBeTran.TXT","r")) == NULL ){printf("Open file fail......\n");exit(0);}if(( CodeFile = fopen("CodeFile.TXT","w")) == NULL ){printf("Open file fail......\n");exit(0);}c = fgetc(ToBeTran); //从文件读取一个字符while( c != EOF ){ //对文件中的各个字符进行编码,直至文件结尾i = 1;while( c != ch[i] && ch[i] != '\0' ) //在ch[]数组中查找从文件读取的字符i++;if(ch[i] == '\0'){ //未找到,c不在ch[]数组中,c无法被识别,退出printf("字符%c无法识别\n",c);exit(0);}fputs(HC[i],CodeFile); //若找到,则将c相应的赫夫曼编码写入到文件中printf("%s",HC[i]); //将c相应的赫夫曼编码输出到屏幕c = fgetc(ToBeTran); //读入文件中的下一个字符}printf("\n");fclose(ToBeTran);fclose(CodeFile);}//译码,翻译成相应的字符表示,并存储到文件------------------------------int p,i = 1;void Decoding(HuffmanTree &HT, char ch[] , int n){char code[1000],c;p = 2 * n - 1; //n为叶子数量,2*n-1即为根节点的节点号FILE *CodeFile,*TextFile;if(( CodeFile = fopen("CodeFile.TXT","r")) == NULL ){printf("Open file fail......\n");exit(0);}if(( TextFile = fopen("TextFile.TXT","w")) == NULL ){printf("Open file fail......\n");exit(0);}c = fgetc(CodeFile);while( c != EOF ){ //从文件读取字符,存储在code[]数组中code[i] = c;i++;c = fgetc(CodeFile);}code[i] = '\0';i = 1;while ( code[i] != '\0' && p != 0 ){ //对数组code[]中的赫夫曼编码进行译码,p储存节点号if ( code[i] == '0' )p=HT[p].lchild; //进入左分支elsep = HT[p].rchild; //进入右分支if (!HT[p].lchild&& !HT[p].rchild){ //不再有叶子节点时(进入叶子结点)fputc(ch[p],TextFile); //将相应的字符写入到文件中printf("%c",ch[p]); //将相应的字符输出到屏幕p = 2 * n - 1; //重新从树根出发进行译码}i++;}printf("\n");}//从文件读取赫夫曼树-------------------------------------------------------------------------------------void ReadHuff_T( HuffmanTree &HT, HuffmanCode &HC, char ch[], int &n){FILE *hfmTree;char c[100],ch1;int i,j,t;if(( hfmTree = fopen("hfmTree.TXT","r")) == NULL ){ //打开存有赫夫曼树信息的文件printf("Open file fail......\n");exit(0);}fgets(c,10,hfmTree); //获取赫夫曼树叶子结点个数(字符串)i = 0; //将字符串形式转换成整数形式while( c[i] != '\n' )i++;n = 0;for( j = 0; j < i; j++ )n = 10 * n + c[j] - '0'; //求出叶子结点数nHC = (HuffmanCode)malloc((n + 1) * sizeof(char *)); //申请HC空间HT = (HuffmanTree)malloc((2 * n) * sizeof(HTNode)); //申请赫夫曼树存储空间i = 1;while( i <= n ){ch[i] = fgetc(hfmTree); //读取字符集中的一个字符HC[i] = (char *)malloc((10)*sizeof(char)); //申请用于存储读取到的字符集中的字符的赫夫曼编码的空间fgetc(hfmTree); //将‘\t’读取并输出ch1 = fgetc(hfmTree); //读取赫夫曼编码,存储在相应的HC[i][]数组里int j = 0;while( ch1 != '\n' ){HC[i][j] = ch1;j++;ch1 = fgetc(hfmTree);}HC[i][j] = '\0';i++;}ch[i] = '\0';i = 0;while( i < 2 * n - 1 ){ //读取赫夫曼树的各个结点的parent,lchild,rchild.并赋值到赫夫曼树HT中ch1 = fgetc(hfmTree); //读取parent的字符串形式,存储在c[]中,并将其转换成整数形式,赋给HT[i].parent j = 0;while( ch1 != ' ' ){ //遇到空格前将读取的字符都保存在c[]中c[j] = ch1;j++;ch1 = fgetc(hfmTree);}HT[i+1].parent = 0; //初始化for( t = 0; t < j; t++ ) //字符转整型HT[i+1].parent = 10 * HT[i+1].parent + c[t] - '0';ch1 = fgetc(hfmTree); //读取lchild的字符串形式,并将其转换成整数形式,赋给HT[i].lchild j = 0;while( ch1 != ' ' ){ //遇到空格前将读取的字符都保存在c[]中c[j] = ch1;j++;ch1 = fgetc(hfmTree);}HT[i+1].lchild = 0; //初始化for( t = 0; t < j; t++ ) //字符转整型HT[i+1].lchild = 10 * HT[i+1].lchild + c[t] - '0';ch1 = fgetc(hfmTree); //读取rchild的字符串形式,并将其转换成整数形式,赋给HT[i].rchild j = 0;while( ch1 != '\n' ){ //换行前将读取的字符都保存在c[]中c[j] = ch1;j++;ch1 = fgetc(hfmTree);}HT[i+1].rchild = 0; //初始化for( t = 0; t < j; t++ ) //字符转整型HT[i+1].rchild = 10 * HT[i+1].rchild + c[t] - '0';i++;}}//显示规定格式编码,并将所得编码存储到文件-----------------------------------void CodePrin(){FILE *CodePrin,*CodeFile;char c;int prin[200];int k=0;if(( CodeFile = fopen("CodeFile.TXT","r")) == NULL ){printf("Open file fail......\n");exit(0);}if(( CodePrin = fopen("CodePrin.TXT","w")) == NULL ){printf("Open file fail......\n");exit(0);}c = fgetc(CodeFile); //从文件读取一个字符while( c != EOF ){ //对文件中的各个字符进行编码,直至文件结尾if(c=='1') prin[k]=1;else prin[k]=0;fputc(c,CodePrin); //将c相应的赫夫曼编码写入到文件中printf("%d",prin[k]); //将c相应的赫夫曼编码输出到屏幕c = fgetc(CodeFile); //读入文件中的下一个字符k++;if(k%50==0){ //50个字符时,换行printf("\n");fputc('\n',CodePrin);}}printf("\n");fclose(CodePrin);fclose(CodeFile);}//显示并打印赫夫曼树----------------------------------------------------------------void TreePrint(HuffmanTree &HT, int n) {FILE *TreePrint;char a[20];int temp;int j;if(( TreePrint = fopen("TreePrint.TXT","w")) == NULL ){ //打开用于存储赫夫曼树的文件printf("Open file fail......\n");exit(0);}for(i = 1; i <= 2 * n - 1; i++ ){ //将赫夫曼树各个结点的parent,lchild,rchild分别显示printf("%d ",HT[i].parent);printf("%d ",HT[i].lchild);printf("%d",HT[i].rchild);printf("\n");temp = HT[i].parent; //将i结点的parent转换成字符并写入到文件中if(temp == 0){fputc(temp + 48,TreePrint);fputc(' ',TreePrint);}else{j = 0; //计算HT[i].parent数字位数while( temp != 0 ){temp = temp / 10;j++;}temp = HT[i].parent; //数字转字符a[j] = '\0';a[j - 1] = (char)(temp % 10 + 48);temp = temp / 10;j--;}fputs(a,TreePrint);fputc(' ',TreePrint);}temp = HT[i].lchild; //将i结点的lchild转换成字符并写入到文件中if(temp == 0){fputc(temp + 48,TreePrint);fputc(' ',TreePrint);}else{j = 0; //计算HT[i].lchild数字位数while( temp != 0 ){temp = temp / 10;j++;}temp = HT[i].lchild; //数字转字符a[j] = '\0';while( temp != 0 ){a[j - 1] = (char)(temp % 10 + 48);temp = temp / 10;j--;}fputs(a,TreePrint);fputc(' ',TreePrint);}temp = HT[i].rchild; //将i结点的rchild转换成字符并写入到文件中if(temp == 0){fputc(temp + 48,TreePrint);fputc('\n',TreePrint);}else{j = 0; //计算HT[i].lchild数字位数while( temp != 0 ){temp = temp / 10;j++;}temp = HT[i].rchild; //数字转字符a[j] = '\0';a[j - 1] = (char)(temp % 10 + 48);temp = temp / 10;j--;}fputs(a,TreePrint);fputc('\n',TreePrint);}}fclose(TreePrint);}//主函数----------------------------------------------------------------------int main(){HuffmanTree HT;HuffmanCode HC;char ch[100]; //用于存储字符集int n,Init_Mode = No; //n为字符集的大小,Init_Mode = No 表示内存中没有赫夫曼树的信息char mode; //让用户选择不同的操作printf("请输入你要选择的功能(命令需要大写)\n");printf("I -- 初始化 E -- 编码\n");printf("D -- 译码P -- 显示并打印编码\n");printf("T -- 显示并打印赫夫曼树Q -- 退出程序\n");scanf("%c",&mode); //获得用户选择的操作while( mode != 'Q' ){ //当用户输入不为Q时,执行相应操作switch(mode){case 'I' :InitHuff_T(HT,HC,ch,n);Init_Mode = Yes;break;case 'E' :if( No == Init_Mode )ReadHuff_T(HT,HC,ch,n);Encoding(HT,HC,ch);Init_Mode = Yes;break;case 'D' :if( No == Init_Mode )ReadHuff_T(HT,HC,ch,n);Decoding(HT,ch,n);Init_Mode = Yes;break;case 'P' :CodePrin();break;case 'T' :if( No == Init_Mode )ReadHuff_T(HT,HC,ch,n);TreePrint(HT,n);Init_Mode = Yes;break;default :printf("您的输入有错,请重新选择.\n");}printf("请输入你要选择的功能\n");printf("I -- 初始化 E -- 编码\n");printf("D -- 译码P -- 显示并打印编码\n"); printf("T -- 显示并打印赫夫曼树Q -- 退出程序\n");fflush(stdin);scanf("%c",&mode); //继续选择相应的操作,直至用户选择退出}return 0;}成绩评定表。