实验四、Huffman编码实现文本压缩
实验题目:Huffman树及Huffman编码的算法实现
实验目的:
1、 了解该树的应用实例,熟悉掌握Huffman树的构造方法及Huffman编码的应用。
2、 了解Huffman树在通信、编码领域的应用过程。
实验要求:
1、输入一段100—200字的英文短文,存入一文件a中。
2、写函数统计短文出现的字符个数n及每个字符的出现次数
3、写函数以字符出现次数作权值,建Haffman树(n个叶子),给出每个字母的Haffman编码。
4、用每个字符编码对原短文进行编码,码文存入文件b中。
5、用Haffman树对b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。
实验内容和实验步骤:
- 关键代码
#include<string> #include<iostream> #include<stdlib.h> using namespace std; #ifndef HEAD_H_INCLUDED #define HEAD_H_INCLUDED #define ASCIISize 127 extern char *FileArray;//储存读取的文件 extern int FileArraySize;//储存读取的文件的数组长度 extern int DecodeFileArraySize;//解码后的文章字符数; //存储解码后的字符 extern char **FileCode;//文章中每个字符的编码,按顺序存储在数组中 extern int FileCodeSize;//文章编码数 extern char *decode; extern int weight[ASCIISize];//叶子节点的权值 typedef struct { unsigned int weight; unsigned int parent,lchild,rchild; char ch;//当前节点代表的字符 } HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树 typedef char **HuffmanCode;//动态分配数组存储哈夫曼编码表 void ReadFile(char path[], char way[], char* &FileArray, int &FileArraySize); void Weight(char* FileArray, int weight[ASCIISize]); void Select(HuffmanTree HT,int i, int &s1,int &s2); void StrCpy(char *hc, const char*c); void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int weight[ASCIISize]); void BuildFileCode(char **&FileCode,int &FileCodeSize,HuffmanCode HC); void DecodeFile(char *& decode,char **FileCode, HuffmanTree HT ); void WriteFileCode(char path[],char way[],char **FileCode);//写码文 void WriteFileDecode(char path[],char way[],char *decode);//写翻译后的文章 #endif // HEAD_H_INCLUDED
#include<iostream> #include<fstream> #include<stdlib.h> #include<string.h> #include"head.h" #include<algorithm> using namespace std; /** 声明变量 **/ char *FileArray;//储存读取的文件 int FileArraySize = 200;//储存读取的文件的数组长度 int DecodeFileArraySize; //存储解码后的字符 char **FileCode;//文章中每个字符的编码,按顺序存储在数组中 int FileCodeSize=20;//文章编码数 char *decode; int weight[ASCIISize];//叶子节点的权值 /** 读取文件 并将文件中的内容存入FileArray数组中 **/ void ReadFile(char path[], char way[], char* &FileArray, int &FileArraySize) { //path文件位置,way文件打开方式(r,w,a...),文件中读取的数据放入FileArray数组中 FILE *fp; int i = 0; //根据路径和打开方式打开指定的文件,判断操作是否成功 if ((fp = fopen(path, way)) == NULL) { cout<<"can't open the file"; } FileArray = (char*)malloc(FileArraySize *sizeof(char)); char ch; while ((ch = fgetc(fp)) != EOF) { if (i + 1 == FileArraySize) { FileArray = (char*)realloc(FileArray, FileArraySize * 2 *sizeof(char)); FileArraySize *= 2; } FileArray[i] = ch; i++; } FileArraySize = i; fclose(fp); //完成读取以后将文件关闭 } /** 求每个ASCII码对应字符的权值,并将权值存入weigh数组中 **/ void Weight(char* FileArray, int weight[ASCIISize]) { for (int i = 0,m; i < FileArraySize; i++) { m = FileArray[i] - 0;//字符对应的number,字符从小到大排列 weight[m]++; } } void StrCpy(char *hc, const char*c) { char *s=hc; while(*c!='\0') *s++=*c++; *s='\0'; } void Select(HuffmanTree HT, int i, int& s1, int& s2) { int j; int w1, w2, s; //选出最小 s1 for (j = 1; j <= i; j++) if (HT[j].parent == 0) break; s1 = j; w1 = HT[j].weight; for (j++; j <= i; j++) if (HT[j].parent == 0 && HT[j].weight < w1) s1 = j, w1 = HT[j].weight; //选出次小 s2 for (j = 1; j <= i; j++) if (HT[j].parent == 0 && j != s1) break; s2 = j; w2 = HT[j].weight; for (j++; j <= i; j++) if (HT[j].parent == 0 && j != s1&&HT[j].weight<w2) s2 = j, w2 = HT[j].weight; //保持s1<s2 if (s1>s2) s = s1, s1 = s2, s2 = s; } /** w存放n个字符的权值(不一定>0),构造哈夫曼树HT,并求出n个字符法哈夫曼编码HC **/ void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int weight[ASCIISize]) { HuffmanTree p; int s1,s2,i,c,f,start; if(ASCIISize<=1) return; int m=2*ASCIISize-1; //节点个数 HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));// *w=weight[],0号单元未用 for(p=HT+1,i=1; i<=ASCIISize; ++i,++p) //对霍夫曼树叶子节点赋权值 { p->weight=weight[i]; p->lchild=0; p->rchild=0; p->parent=0; p->ch=i;//这个节点所对应的字符 } for(; i<=m; ++i,++p) //对二度节点赋初始值0 { p->weight=0; p->lchild=0; p->rchild=0; p->parent=0; p->ch=-1;//二度节点没有代表字符 } for(i=ASCIISize+1; i<=m; ++i) //从二度节点开始赋值 { Select(HT,i-1,s1,s2);//HT[1,,i-1]中选择parent为0且weight最小的两个节点,其序号分别为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((ASCIISize+1)*sizeof(char *)); char *cd=(char *)malloc(ASCIISize*sizeof(char)); for(i=1; i<=ASCIISize; ++i) { start=ASCIISize-1; cd[start]='\0'; for(c=i,f=HT[i].parent; f!=0; c=f,f=HT[f].parent) //从叶子到根逆向求编码 { if(HT[f].lchild==c)//当前节点是左孩子 { cd[--start]='0'; } else cd[--start]='1';//当前节点是右孩子 }//直到当前节点的parent是0 HC[i]=(char*)malloc((ASCIISize-start)*sizeof(char));//为第i个字符编码分配空间 StrCpy(HC[i],&cd[start]);//从cd复制编码到HC[i] } free(cd);//释放工作空间 for(i=1; i<=ASCIISize; i++) cout<<HC[i]<<" i="<<i<<" weight[i]="<<weight[i]<<endl; } /** 根据得到的每个字母的编码,求整篇文章的编码,结果存入FileCode[]数组中 **/ void BuildFileCode(char **&FileCode,int &FileCodeSize,HuffmanCode HC) { FileCode=(char **)malloc(FileCodeSize*sizeof(char *)); int m=0,n=0,i; for(i=0; i<FileArraySize; i++,n++) { if(n+1>=FileCodeSize) { FileCodeSize *= 2; FileCode=(char **)realloc(FileCode,FileCodeSize*sizeof(char *)); } m=FileArray[i]-0; FileCode[n]=HC[m]; cout<<"FileCode["<<n<<"]="<<FileCode[n]<<endl; } FileCode[n]="###";//文章编码结尾 FileCodeSize=n;//文章编码数是n } /** 根据整篇文章的编码和Huffman树,进行解码,存入数组decode[]中 **/ void DecodeFile(char *& decode,char **FileCode, HuffmanTree HT ) { int p,root,j=0,i,k; char fc; root=p=2*ASCIISize-1;//遍历从根节点开始 decode=(char *)malloc(FileCodeSize*sizeof(char)); for(i=0; FileCode[i]!="###"; i++) //直到解码到文章末尾 { int m=strlen(FileCode[i]); for(k=0; k<m; k++) { fc = *(FileCode[i]++); if(fc=='1')//当前节点是原节点的右孩子 p=HT[p].rchild; else if(fc=='0')//当前节点是原节点的左孩子 p=HT[p].lchild; if(HT[p].ch!=-1)//当前节点是叶子节点 { decode[j++]=HT[p].ch;//解码得到的第j个字符是HT[p].ch // int k=j-1; // cout<<decode[k]; p=root;//解码完一个字符之后,回到根节点 } } DecodeFileArraySize=j; } } /** 将编码后的文件(码文)写入b.txt **/ void WriteFileCode(char path[],char way[],char **FileCode)//写码文 { FILE *fp; fp=fopen(path,way); int n=0; while(n < FileCodeSize) { fputs(FileCode[n],fp);//字符串 n++; } fclose(fp); } /** 存储文件,将decode[]存入解码后的文件中 **/ void WriteFileDecode(char path[],char way[],char *decode) { FILE *fp; fp=fopen(path,way); for(int i=0; i<DecodeFileArraySize; i++) { cout<<decode[i]; fputc(decode[i],fp); } fclose(fp); }
#include<iostream> #include<fstream> #include"head.h" using namespace std; int main() { HuffmanCode HC; HuffmanTree HT; char a='a'; int m=a-0; cout<<m<<endl; ReadFile("F:\\a.txt","r",FileArray,FileArraySize); Weight(FileArray, weight); for(int i=1;i<=ASCIISize;i++) cout<<weight[i]<<" "; // HuffmanCode HC; // HuffmanTree HT; HuffmanCoding(HT,HC,weight); // BuildFileCode(FileCode,FileCodeSize,HC); WriteFileCode("F:\\b.txt","a",FileCode); // DecodeFile(decode, FileCode, HT ); WriteFileDecode("F:\\c.txt","a",decode); return 0; }
实验用测试数据和相关结果分析:
上图分别是文章中每个字符的编码(weight[i]),文章的编码(FileCode[i]),译码后的文章。我用了一篇伊索寓言存在a.txt中,在相应目录下可以分别得到编码后的文章b.txt和译码后的文章c.txt,经检查后a和c中的内容完全一致。
实验总结:
- 我在编码的时候将每个ASCII码都编了一遍,一共128个,不管他们有没有在文章中出现。这样的缺点是会浪费很多weight数组的空间,以及哈夫曼树会很庞大,但是编码后的文章却与此无关,而且这样可以不限文章内容,比较方便。目前我还不知道怎样通过计算文章中出现的字符个数来进行选择性的编码,所以之后还要继续学习。
- 除了weight数组之外,其余数组均为动态数组,这也让我学习了如何灵活运用动态数组。以及用数组的地址做函数参数,改变数组的内容等。
- 学习了有关指针的一些操作,还有文件读写。