哈夫曼树解压与压缩

合集下载

哈夫曼树解压与压缩

哈夫曼树解压与压缩

哈夫曼树的压缩与解压1.算法简要描述1.哈弗曼算法是根据给定的n个权值{w1,w2,w3.......wn},构造由n棵二叉树构成的深林F={T1,T2,。

Tn},其中每个二叉树Ti分别都是只含有一个权值wi的根结点,其左右子树为空〔i=1,,,,,,2〕。

2.在深林F中选取其根结点的权值最小的两棵二叉树,分别作其左右子树构造一颗新的二叉树,并置这棵新的二叉树根结点的权值为其左右子树的根结点之和。

3.从F中删去这两棵二叉树,同时刚新生成的二叉树参加到深林F中。

4.重复2,3,步骤,直至深林F中只含有一颗二叉树为止。

函数String EnCode(Char Type ch):表示哈夫曼树已存在,返回字符ch的编码。

函数LinkList<CharType>UnCode(String strCode):表示对哈夫曼树进展译码,返回编码前的字符序列。

根据算法可以看出,在具有n个结点权值的哈夫曼树的构造过程中,每次都是从F中删去两棵树,增加一棵树,即每次完毕后减少一棵树,经过n-1次处理后,F中就只剩下一棵树了。

另外,每次合并都要产生一个新的结点,合并n-1次后共产生了n-1个新结点,并且这n-1个新节点都是具有左右子树的分支结点。

如此最终得到的哈夫曼树中共有2n-1个结点,并且其中没有度为1的分支结点,最后一次产生的新结点就是哈夫曼树的根结点。

源代码中创建了一个哈夫曼树结点类,其中有数据成员weight,parent,leftChild,rightChild分别代表了权值,双亲,左孩子,右孩子。

在哈夫曼树类中有数据成员*nodes,*LeafChars,*LeafCharCodes,curPos,num,分别用来存储结点信息,叶结点字符信息,叶结点字符编码信息,译码时从根结点到叶结点路径的当前结点,叶结点个数。

哈夫曼树类中含有多个函数,有构造函数,析构函数等。

由函数HuffmanTree(CharType ch[],WeightType w[],int n)来构造由字符,权值,和字符个数构造哈夫曼树,在根据哈夫曼算法很容易实现哈夫曼类的函数以与构造函数。

利用哈夫曼编码实现压缩和解压缩

利用哈夫曼编码实现压缩和解压缩

利用哈夫曼编码实现压缩和解压缩1.问题描述利用哈夫曼编码,实现压缩和解压缩的数据元素具有如下形式:结点:weight:存储结点的权值parent:是结点双亲在向量中的下标lchild:结点的左儿子向量下标rchild:结点右儿子向量下标bits:位串,存放编码ch:字符start:编码在位串中的起始位置文件操作记录:b:记录字符在数组中的位置count:字符出现频率(权值)lch、rch、parent:定义哈夫曼树指针变量bits[256]:定义存储哈夫曼编码的数组2.功能需求对于给定的一组字符,可以根据其权值进行哈夫曼编码,并能输出对应的哈夫曼树和哈夫曼编码;实现哈夫曼解码。

能够分析文件,统计文件中出现的字符,再对文件进行编码,实现文件的压缩和解压缩,能够对于文件的压缩,比例进行统计,能够打印文件。

3.实现要点(1)构造哈弗曼树过程中,首先将初始森林的各根结点的双亲和左、右儿子指针置-1;叶子在向量T的前n个分量中,构成初始森林的n个结点;对森林中的树进行n次合并,并产生n-1个新结点,依次放入向量T的第i个分量中。

(2)编码过程中,从叶子T[i]出发,利用双亲的指针找到双亲T[p];再根据T[p]的孩子指针可以知道T[i]是T[p]的左儿子还是右儿子,若是左儿子,则生成代码0,否则生成代码1;(3)在文件压缩和解压过程中,主要参考网上资料,每个步骤都基本理解,并注上了详细解析。

4.函数定义功能:输入权重,构造一棵哈弗曼树void huffman(hftree T){if(n<1 || n > m)return;int i,j,p1,p2;float small1,small2;//初始化cout<<"请输入叶子权重(5个):"<<endl;for(i=0; i<n; i++){T[i].parent = -1;T[i].lchild = T[i].rchild = -1;}//输入叶子权值for(i=0; i<n; i++){cin>>T[i].weight;}for(i=n; i<m; i++){p1 = p2 = -1;small1 = small2 = MAX_FLOAT;for(j=0; j<=i-1; j++){if(T[j].parent != -1)continue;if(T[j].weight < small1){small2 = small1;small1 = T[j].weight;p2 = p1;p1 = j;}else if(T[j].weight < small2){small2 = T[j].weight;p2 = j;}}T[p1].parent = T[p2].parent = i;T[i].parent=-1;T[i].lchild=p1;T[i].rchild=p2;T[i].weight=small1 + small2;}cout<<"创建成功!"<<endl;}功能:对哈弗曼树进行编码void encode(codelist codes, hftree T){int i,c,p,start;cout<<"请输入需要编码的字符(5个):"<<endl;for(i=0; i<n; i++){cin>>codes[i].ch;start=n;c=i;p=T[i].parent;while(p!=-1){start--;if(T[p].lchild==c) codes[i].bits[start]='0';else codes[i].bits[start]='1';c=p;p=T[p].parent;}codes[i].start=start;}cout<<"输入成功!:"<<endl;cout<<"编码表:"<<endl;for(int x=0; x<n; x++){cout<<codes[x].ch<<": ";for(int q=codes[x].start;q<n;q++) cout<<codes[x].bits[q];cout<<endl;}}函数功能:对哈弗曼树进行解码void decode(codelist codes,hftree T) {int i,c,p,b;int endflag;endflag=-1;i=m-1;while(cin>>b,b!=endflag){if(b==0) i=T[i].lchild;else i=T[i].rchild;if(T[i].lchild==-1){cout<<codes[i].ch;i=m-1;}}if(i!=m-1)cout<<"编码有错!\n";}功能:对文件进行压缩,统计压缩率void compress(){char filename[255],outputfile[255],buf[512];unsigned char c;long i,j,m,n,f;long min1,pt1,flength,length1,length2;double div;FILE *ifp,*ofp;cout<<"\t请您输入需要压缩的文件:";cin>>filename;ifp=fopen(filename,"rb");if(ifp==NULL){cout<<"\n\t文件打开失败!\n\n";return;}cout<<"\t请您输入压缩后的文件名:";cin>>outputfile;ofp=fopen(strcat(outputfile,".encode"),"wb");if(ofp==NULL){cout<<"\n\t压缩文件失败!\n\n";return;}flength=0;while(!feof(ifp)){fread(&c,1,1,ifp);header[c].count++; //字符重复出现频率+1flength++; //字符出现原文件长度+1}flength--;length1=flength; //原文件长度用作求压缩率的分母header[c].count--;for(i=0;i<512;i++){if(header[i].count!=0) header[i].b=(unsigned char)i;else header[i].b=0;header[i].parent=-1;header[i].lch=header[i].rch=-1; //对结点进行初始化}for(i=0;i<256;i++) //根据频率(权值)大小,对结点进行排序,选择较小的结点进树{for(j=i+1;j<256;j++){if(header[i].count<header[j].count){tmp=header[i];header[i]=header[j];header[j]=tmp;}}}for(i=0;i<256;i++) if(header[i].count==0) break;n=i; //外部叶子结点数为n个时,内部结点数为n-1,整个哈夫曼树的需要的结点数为2*n-1.m=2*n-1;for(i=n;i<m;i++) //构建哈夫曼树{min1=999999999; //预设的最大权值,即结点出现的最大次数for(j=0;j<i;j++){if(header[j].parent!=-1) continue;//parent!=-1说明该结点已存在哈夫曼树中,跳出循环重新选择新结点*/if(min1>header[j].count){pt1=j;min1=header[j].count;continue;}}header[i].count=header[pt1].count;header[pt1].parent=i; //依据parent域值(结点层数)确定树中结点之间的关系header[i].lch=pt1; //计算左分支权值大小min1=999999999;for(j=0;j<i;j++){if(header[j].parent!=-1) continue;if(min1>header[j].count){pt1=j;min1=header[j].count;continue;}}header[i].count+=header[pt1].count;header[i].rch=pt1; //计算右分支权值大小header[pt1].parent=i;}for(i=0;i<n;i++) //哈夫曼无重复前缀编码{f=i;header[i].bits[0]=0; //根结点编码0while(header[f].parent!=-1){j=f;f=header[f].parent;if(header[f].lch==j) //置左分支编码0{j=strlen(header[i].bits);memmove(header[i].bits+1,header[i].bits,j+1);//依次存储连接“0”“1”编码header[i].bits[0]='0';}else //置右分支编码1{j=strlen(header[i].bits);memmove(header[i].bits+1,header[i].bits,j+1);header[i].bits[0]='1';}}}fseek(ifp,0,SEEK_SET); //从文件开始位置向前移动0字节,即定位到文件开始位置fwrite(&flength,sizeof(int),1,ofp);fseek(ofp,8,SEEK_SET);buf[0]=0; //定义缓冲区,它的二进制表示00000000f=0;pt1=8;while(!feof(ifp)){c=fgetc(ifp);f++;for(i=0;i<n;i++){if(c==header[i].b) break;}strcat(buf,header[i].bits);j=strlen(buf);c=0;while(j>=8) //对哈夫曼编码位操作进行压缩存储{for(i=0;i<8;i++){if(buf[i]=='1') c=(c<<1)|1;else c=c<<1;}fwrite(&c,1,1,ofp);pt1++; //统计压缩后文件的长度strcpy(buf,buf+8); //一个字节一个字节拼接j=strlen(buf);}if(f==flength) break;}if(j>0) //对哈夫曼编码位操作进行压缩存储{strcat(buf,"00000000");for(i=0;i<8;i++){if(buf[i]=='1') c=(c<<1)|1;else c=c<<1;}fwrite(&c,1,1,ofp);pt1++;}fseek(ofp,4,SEEK_SET);fwrite(&pt1,sizeof(long),1,ofp);fseek(ofp,pt1,SEEK_SET);fwrite(&n,sizeof(long),1,ofp);for(i=0;i<n;i++){fwrite(&(header[i].b),1,1,ofp);c=strlen(header[i].bits);fwrite(&c,1,1,ofp);j=strlen(header[i].bits);if(j%8!=0) //若存储的位数不是8的倍数,则补0 {for(f=j%8;f<8;f++)strcat(header[i].bits,"0");}while(header[i].bits[0]!=0){c=0;for(j=0;j<8;j++) //字符的有效存储不超过8位,则对有效位数左移实现两字符编码的连接{if(header[i].bits[j]=='1') c=(c<<1)|1; //|1不改变原位置上的“0”“1”值else c=c<<1;}strcpy(header[i].bits,header[i].bits+8); //把字符的编码按原先存储顺序连接fwrite(&c,1,1,ofp);}}length2=pt1--;div=((double)length1-(double)length2)/(double)length1; //计算文件的压缩率fclose(ifp);fclose(ofp);printf("\n\t压缩文件成功!\n");printf("\t压缩率为 %f%%\n\n",div*100);return;}函数功能:对文件解压缩void uncompress(){char filename[255],outputfile[255],buf[255],bx[255];unsigned char c;long i,j,m,n,f,p,l;long flength;FILE *ifp,*ofp;cout<<"\t请您输入需要解压缩的文件:";cin>>filename;ifp=fopen(strcat(filename,".encode"),"rb");if(ifp==NULL){cout<<"\n\t文件打开失败!\n";return;}cout<<"\t请您输入解压缩后的文件名:";cin>>outputfile;ofp=fopen(outputfile,"wb");if(ofp==NULL){cout<<"\n\t解压缩文件失败!\n";return;}fread(&flength,sizeof(long),1,ifp); //读取原文件长度,对文件进行定位fread(&f,sizeof(long),1,ifp);fseek(ifp,f,SEEK_SET);fread(&n,sizeof(long),1,ifp);for(i=0;i<n;i++){fread(&header[i].b,1,1,ifp);fread(&c,1,1,ifp);p=(long)c; //读取原文件字符的权值header[i].count=p;header[i].bits[0]=0;if(p%8>0) m=p/8+1;else m=p/8;for(j=0;j<m;j++){fread(&c,1,1,ifp);f=c;itoa(f,buf,2); //将f转换为二进制表示的字符串f=strlen(buf);for(l=8;l>f;l--){strcat(header[i].bits,"0");}strcat(header[i].bits,buf);}header[i].bits[p]=0;}for(i=0;i<n;i++) //根据哈夫曼编码的长短,对结点进行排序{for(j=i+1;j<n;j++){if(strlen(header[i].bits)>strlen(header[j].bits)){tmp=header[i];header[i]=header[j];header[j]=tmp;}}}p=strlen(header[n-1].bits);fseek(ifp,8,SEEK_SET);m=0;bx[0]=0;while(1) //通过哈夫曼编码的长短,依次解码,从原来的位存储还原到字节存储{while(strlen(bx)<(unsigned int)p){fread(&c,1,1,ifp);f=c;itoa(f,buf,2);f=strlen(buf);for(l=8;l>f;l--) //在单字节内对相应位置补0{strcat(bx,"0");}strcat(bx,buf);}for(i=0;i<n;i++){if(memcmp(header[i].bits,bx,header[i].count)==0) break;}strcpy(bx,bx+header[i].count);c=header[i].b;fwrite(&c,1,1,ofp);m++; //统计解压缩后文件的长度if(m==flength) break; //flength是原文件长度}fclose(ifp);fclose(ofp);cout<<"\n\t解压缩文件成功!\n";if(m==flength) //对解压缩后文件和原文件相同性比较进行判断(根据文件大小)cout<<"\t解压缩文件与原文件相同!\n\n";else cout<<"\t解压缩文件与原文件不同!\n\n";return;}5、总结和体会本次大作业与C++大作业有所不同,主要是利用构造数据结构解决问题,C++大作业主要体现类和文件读写功能。

c++哈夫曼树的文件压缩解压程序全部代码及设计报告

c++哈夫曼树的文件压缩解压程序全部代码及设计报告

#include <iostream>#include <fstream>#include <queue> //队列容器using namespace std;const int leaf = 256; //最多可能出现的不同字符数const long MAX = 99999999; //表示无穷大typedef struct HTnode{long weight; //记录结点的权值int parent; //记录结点的双亲结点位置int lchild; //结点的左孩子int rchild; //结点的右孩子int *code; //记录该结点的huffman编码int codelen; //记录该结点huffman编码的长度HTnode(){weight = MAX;parent = -1;lchild = -1;rchild = -1;codelen = 0;}}HTnode;class huffmanTree{public:huffmanTree();virtual ~huffmanTree();bool count(char *input); //统计各字符出现的次数,将其写入对应结点的权值void create(); //构造huffman树void code(); //计算每个字符的huffman编码void addbit(int bit); //压缩时对一个未满8个bit的byte中加入一个bitvoid resetbyte(); //将byte清空bool compress(char *input, char *output); //压缩函数成功执行返回true 失败falsebool decompress(char *input, char *output); //解压函数成功执行返回true 失败falsevoid compare(char *input, char *output); //将原文件与压缩后的文件比较private:int root; //记录根结点的位置int leafnum; //记录不同字符的个数HTnode HT[leaf*2-1]; //HTnode结构的数组,用来表示huffman树,树的最大结点个数不会超过leaf*2-1char byte; //压缩文件时用来缓冲bit的变量int bitsnum; //byte中bit的个数int lacknum; //压缩到最后byte中的bit不满8个时填充的0的个数};huffmanTree::huffmanTree(){//初始化成员变量root = 0;leafnum = 0;byte = 0;bitsnum = 0;lacknum = 0;}huffmanTree::~huffmanTree(){for(int i=0; i<leaf; i++){if(HT[i].codelen != 0)delete []HT[i].code;}}//统计各字符出现的次数bool huffmanTree::count(char *input){ifstream ifs;char c;ifs.open(input,ios::binary);if(!ifs){cout << "无法打开文件" << input << '!' << endl;return false;}while(ifs.get(c)){if(HT[c+128].weight==MAX){ //若该字符是第一次出现,先初始化权值HT[c+128].weight = 0;leafnum++;}HT[c+128].weight++; //权值+1}ifs.close();return true;}//选权值最小的两棵树组成新的数void huffmanTree::create(){for(int i=leaf; i<2*leaf-1; i++){int loc1=-1, loc2=-1;for(int j=0; j<i; j++){if(HT[j].parent != -1)continue;if(loc1==-1 || HT[j].weight < HT[loc1].weight){loc2 = loc1;loc1 = j;}else if(loc2==-1 || HT[j].weight < HT[loc2].weight)loc2 = j;}if(HT[loc1].weight==MAX || HT[loc2].weight==MAX || loc2==-1) //只剩一棵树,结束break;HT[i].weight = HT[loc1].weight + HT[loc2].weight;//为了减少压缩文件中需要写入的huffman树的信息,约定小标小的结点做为双亲结点的左孩子HT[i].lchild = loc1>loc2 ? loc2 : loc1;HT[i].rchild = loc1>loc2 ? loc1 : loc2;HT[loc1].parent = i; HT[loc2].parent = i;root = i;}}//计算每个字符的huffman编码void huffmanTree::code(){for(int i=0; i<leaf; i++){int len=0;int loc=i;while(HT[loc].parent!=-1){ //计算huffman编码长度len++;loc = HT[loc].parent;}HT[i].codelen = len;HT[i].code = new int[len];loc = i;for(int j=len-1; j>=0; j--){ //从后往前找,记录结点的huffman编码if(loc==HT[HT[loc].parent].lchild)HT[i].code[j] = 0;elseHT[i].code[j] = 1;loc = HT[loc].parent;}}}//压缩时对一个未满8个bit的byte中加入一个bitvoid huffmanTree::addbit(int bit){if(bit == 0)byte = byte << 1; //若新增的bit为0,则直接将byte按位左移elsebyte = ((byte << 1) | 1); //若新增的bit为1,先将byte按位左移,再与1按位或运算bitsnum++;}//将byte清空void huffmanTree::resetbyte(){byte = 0;bitsnum = 0;}//压缩函数成功执行返回true 失败falsebool huffmanTree::compress(char *input, char *output){if( !count(input) )return false;create();code();ifstream ifs;ofstream ofs;ifs.open(input,ios::binary);ofs.open(output,ios::binary);char c;if(!ifs){cout << "无法打开文件" << input << '!' << endl;return false;}if(!ofs){cout << "无法打开文件" << output << '!' << endl;return false;}ofs.put(0); //预留一个字符,等压缩完后在该位置写入不足一个byte的bit个数ofs.put(root-384); //将根节点的位置-384写入(为使该值不超过char的最大表示范围)for(int i=0; i<leaf*2-1; i++){ //写入每个结点的双亲结点位置if(HT[i].parent==-1) //若该节点没有双亲结点,则写入127(一个字节所能表示的最大值)ofs.put(127);else //否则将双亲结点的位置-384再写入(为使该值不超过char的最大表示范围)ofs.put(HT[i].parent-384);}while(ifs.get(c)){ //将字符的huffman编码并加入byte中int tmp = c+128;for(int i=0; i<HT[tmp].codelen; i++){addbit(HT[tmp].code[i]);if(bitsnum==8){ //若byte已满8位,则输出该byte并将byte清空ofs.put(byte);resetbyte();}}}if(bitsnum!=0){ //处理最后未满8个字符的byte,用0填充并记录填充的个数for(int i=bitsnum; i<8; i++){addbit(0);lacknum++;}ofs.put(byte);resetbyte();}ofs.seekp(0,ios::beg); //将写指针移动到文件开头ofs.put(lacknum); //写入最后一个字节缺失的bit个数ifs.close();ofs.close();return true;}//解压函数成功执行返回true 失败falsebool huffmanTree::decompress(char *input, char *output){queue<char> q;char c;ifstream ifs;ofstream ofs;ifs.open(input,ios::binary);ofs.open(output,ios::binary);if(!ifs){cout << "无法打开文件" << input << '!' << endl;return true;}if(!ofs){cout << "无法打开文件" << output << '!' << endl;return false;}ifs.get(c);lacknum = c; //读出最后一个字节缺失的bit个数ifs.get(c);root = c+384; //读出根结点的位置for(int i=0; i<leaf*2-1; i++){ //建立各结点之间的双亲孩子关系ifs.get(c);if(c==127)continue;else{HT[i].parent = c+384;if(HT[c+384].lchild==-1)HT[c+384].lchild = i;elseHT[c+384].rchild = i;}}int point = root;//为了方便处理最后一个可能有缺失bit的字节,先将读出的数据放入队列while(ifs.get(c))q.push(c);//还原文件过程while(q.size()>1){ //还未到最后一个字节c = q.front();for(int i=0; i<8; i++){if(int(c&128)==0){point = HT[point].lchild;if(HT[point].lchild==-1 && HT[point].rchild==-1){ofs.put(char(point-128));point = root;}c = c << 1;}else{point = HT[point].rchild;if(HT[point].lchild==-1 && HT[point].rchild==-1){ofs.put(char(point-128));point = root;}c = c << 1;}}q.pop();}c = q.front(); //最后一个字节for(i=0; i<8-lacknum; i++){if(int(c&128)==0){point = HT[point].lchild;if(HT[point].lchild==-1 && HT[point].rchild==-1){ofs.put(char(point-128));point = root;}c = c << 1;}else{point = HT[point].rchild;if(HT[point].lchild==-1 && HT[point].rchild==-1){ofs.put(char(point-128));point = root;}c = c << 1;}}q.pop();ifs.close();ofs.close();return true;}//将原文件与压缩后的文件比较void huffmanTree::compare(char *input, char *output){ifstream origin, compress;origin.open(input,ios::binary);compress.open(output,ios::binary);if(!origin){cout << "无法打开文件" << input << '!' << endl;return;}if(!compress){cout << "无法打开文件" << output << '!' << endl;return;}double total1=0, total2=0;char c;while(origin.get(c))total1++;while(compress.get(c))total2++;cout << "原文件大小:" << total1 << " Byte" << endl;cout << "压缩后大小:" << total2 << " Byte" << endl;cout << "压缩率:" << total2/total1*100 << '%' << endl;origin.close();compress.close();}void main(){int choice = 1;char input[255], output[255];huffmanTree h;while(choice){cout<<" ****************************************************"<<endl;cout<<" * 基于哈夫曼树的文件压缩/解压程序*"<<endl;cout<<" * *"<<endl;cout<<" * 1) 压缩*"<<endl;cout<<" * *"<<endl;cout<<" * 2) 解压*"<<endl;cout<<" * *"<<endl;cout<<" * 0) 退出*"<<endl;cout<<" * *"<<endl;cout<<" * 说明:请输入相应的操作序号*"<<endl;cout<<" ****************************************************"<<endl;cout<<"请选择:";cin >> choice;switch(choice){case 1:{cout << "请输入待压缩的文件名:";cin >> input;cout << "请输入压缩后的文件名:";cin >> output;if( press(input,output)){pare(input,output);cout<<"文件压缩成功!"<<endl;}else{cout<<"文件压缩失败!"<<endl;}}break;case 2:{cout << "请输入待解压的文件名:";cin >> input;cout << "请输入解压后的文件名:";cin >> output;if (h.decompress(input,output))cout<<"文件解压成功!"<<endl;elsecout<<"文件解压失败!"<<endl;}break;case 0:break;default:cout << "参数错误!请重新输入" << endl;}cout << endl;}}韶关学院计算机科学学院数据结构课程设计题目:基于哈夫曼树的文件压缩/解压程序学生姓名:曹键明学号:11115011018专业:计算机科学与技术班级:11级(1)班指导教师姓名及职称:陈正铭讲师起止时间:2013 年3 月——2013 年4 月1 需求分析1.1课题背景及意义近年来,随着计算机技术的发展,多媒体计算机技术、计算机网络技术以及现代多媒体通信技术正在向着信息化、高速化、智能化迅速发展。

哈夫曼的文件压缩解压程序

哈夫曼的文件压缩解压程序
个操作只需要写出伪代码算法,画出函数的调 用关系图。 4、 调试分析报告
调试过程中遇到的问题并且是如何解决的 以及对设计实现的回顾讨论和分析算法的时空 分析(包括基本操作和主要算法的时空复杂度 的分析)和改进设想经验和体会等
12
5、 用户使用说明 向用户说明如何使用你编写的程序,详细列出每
一步的操作步骤。 6、 测试结果
8
STL示例——排序算法
(1)自行写排序算法与调用; (2)调用标准C++函数qsort()完成排序; (3)部分使用STL特性; (4)完全使用STL特性。
9
设计要求
基于哈夫曼树的文件压缩/解压程序
基本要求:实现一个基于哈夫曼树的文件压缩程序和文件解 压程序。要求压缩程序读入源文件,分析每种字符的频度, 然后建立相应的哈夫曼树,再求出相应哈夫曼编码,根据 编码对源文件进行压缩,得到源文件对应的压缩文件。解 压程序读入压缩文件,根据相应的哈夫曼编码解压还原 , 得到对应的源文件。
第2~8周 编写程序,准备测试数据
第9周
上机演示,回答教师提问,按格 式撰写课程设计报告
地点
备注
课室
实验室或 在实验室或宿舍完成编 宿舍 写程序任务
实验室
课程设计报告应严格按 照上述设计报告撰写要 求撰写;最后将报告用 A4纸打印装订;
第10周
以班为单位上交课程设计的资料 光盘(内含每个同学的课程设计 的源程序、编译后可直接运行的 目标程序与课程设计报告的电子 文档),课程设计报告打印稿
2 4
5
79
WPL(T)=
74+94+53+
42+21
=89
18
二、如何构造最优树

哈夫曼编码 压缩

哈夫曼编码 压缩

哈夫曼编码压缩标题:哈夫曼编码压缩:一个深度解析一、引言哈夫曼编码是一种用于数据压缩的算法,由戴维·A·哈夫曼在1952年提出。

这种编码方法通过创建一种特殊的二叉树(哈夫曼树)来实现数据压缩。

哈夫曼编码广泛应用于文本文件、音频文件、图像文件等的数据压缩。

二、哈夫曼树的构建哈夫曼树是一种特殊的二叉树,它的特点是左子节点小于父节点,右子节点大于父节点。

哈夫曼树的构建过程如下:1. 初始化:将所有字符及其出现频率作为叶子节点,构成一棵棵只有根节点和一个叶子节点的二叉树。

2. 合并:每次选取两个权值最小的节点,生成一个新的节点,新节点的权值是两个被选取节点权值之和,然后把这两个节点作为新节点的左右孩子。

这样就得到了一颗新的二叉树。

3. 重复第二步,直到只剩下一个节点,这棵树就是我们要找的哈夫曼树。

三、哈夫曼编码哈夫曼编码是指从哈夫曼树的根到每个叶子节点的路径上的0和1的序列。

具体做法是从根节点出发,向左走记为0,向右走记为1。

每个字符的哈夫曼编码就是从根节点到该字符所在叶子节点的路径上的0和1的序列。

四、哈夫曼编码的压缩与解压1. 压缩:对原始数据进行哈夫曼编码,得到压缩后的数据。

2. 解压:对压缩后的数据进行哈夫曼解码,还原出原始数据。

五、哈夫曼编码的优点与缺点优点:1. 数据压缩效率高:哈夫曼编码能够有效地减少数据存储空间,提高数据传输速度。

2. 简单易懂:哈夫曼编码的原理和实现都比较简单,易于理解和实现。

缺点:1. 对于稀疏数据,压缩效果不佳:哈夫曼编码依赖于字符的出现频率,如果字符出现频率相近,压缩效果会降低。

2. 需要额外的存储空间:为了恢复原始数据,需要保存哈夫曼树或者哈夫曼编码表,这会占用一定的存储空间。

六、总结哈夫曼编码是一种有效的数据压缩方法,它通过构建哈夫曼树来实现数据的压缩和解压。

虽然哈夫曼编码有一些缺点,但其高效性和简单性使其在实际应用中得到了广泛的应用。

在未来,随着数据量的不断增大,哈夫曼编码等数据压缩技术将会发挥更大的作用。

哈夫曼树与文件解压压缩C言代码

哈夫曼树与文件解压压缩C言代码

1.问题描述哈弗曼树的编码与译码—功能:实现对任何类型文件的压缩与解码—输入:源文件,压缩文件—输出:解码正确性判定,统计压缩率、编码与解码速度—要求:使用边编码边统计符号概率的方法〔自适应Huffman编码〕和事先统计概率的方法〔静态Huffman编码〕2.1程序清单程序书签:1.main函数2.压缩函数3.select函数4.encode函数5.解压函数#include <stdio.h>#include <string.h>#include <stdlib.h>#include <conio.h>#include <time.h>struct node{long weight; //权值unsigned char ch;//字符int parent,lchild,rchild;char code[256];//编码的位数最多为256位int CodeLength;//编码长度}hfmnode[512];void compress();void uncompress();//主函数void main(){int choice;printf("请选择1~3:\n");printf("1.压缩文件\n");printf("2.解压文件\n");printf("3.退出!\n");scanf("%d",&choice);if(choice==1)compress();else if(choice==2)uncompress();else if(choice==3)return;else printf("输入错误!");}//压缩函数void compress(){int i,j;char infile[20],outfile[20];FILE *ifp,*ofp;unsigned char c;//long FileLength,filelength=0;int n,m;//叶子数和结点数int s1,s2; //权值最小的两个结点的标号char codes[256];long sumlength=0;float rate,speed;int count=0;clock_t start1, start2,finish1,finish2;double duration1,duration2;void encode(struct node *nodep,int n);//编码函数int select(struct node *nodep,int pose);//用于建哈弗曼树中选择权值最小的结点的函数printf("请输入要压缩的文件名:");scanf("%s",infile);ifp=fopen(infile,"rb");if(ifp==NULL){printf("文件名输入错误,文件不存在!\n");return;}printf("请输入目标文件名:");scanf("%s",outfile);ofp=fopen(outfile,"wb");if(ofp==NULL){printf("文件名输入错误,文件不存在!\n");return;}start1=clock() ;//开始计时1//统计文件中字符的种类以与各类字符的个数//先用字符的ASCII码值代替结点下标FileLength=0;while(!feof(ifp)){fread(&c,1,1,ifp);hfmnode[c].weight++;FileLength++;}FileLength--; //文件中最后一个字符的个数会多统计一次,所以要减一hfmnode[c].weight--;//再将ASCII转换为字符存入到结点的ch成员里,同时给双亲、孩子赋初值-1n=0;for(i=0;i<256;i++)if(hfmnode[i].weight!=0){hfmnode[i].ch=(unsigned char)i;n++;//叶子数hfmnode[i].lchild=hfmnode[i].rchild=hfmnode[i].parent=-1;}m=2*n-1;//哈弗曼树结点总数j=0;for(i=0;i<256;i++)//去掉权值为0的结点if(hfmnode[i].weight!=0){hfmnode[j]=hfmnode[i];j++;}for(i=n;i<m;i++)//初始化根结点{hfmnode[i].lchild=hfmnode[i].rchild=-1;hfmnode[i].parent=-1;}//建立哈弗曼树for(i=n;i<m;i++){s1=select(hfmnode,i-1);hfmnode[i].lchild=s1;hfmnode[s1].parent=i;s2=select(hfmnode,i-1);hfmnode[i].rchild=s2;hfmnode[s2].parent=i;hfmnode[i].weight=hfmnode[s1].weight+hfmnode[s2].weight;}//编码encode(hfmnode,n);finish1=clock();duration1=(double)(finish1- start1) / CLOCKS_PER_SEC;/*printf( "哈弗曼树编码用时为:%f seconds\n", duration1 );*/printf("编码完成,是否查看编码信息: y or n?\n");c=getch();if(c=='y'){ printf("\n");printf("叶子数为%d,结点数为%d\n",n,m);for(i=0;i<n;i++)printf("%d号叶子结点的权值为:%ld,双亲为:%d,左右孩子:%d,编码为:%s\n",i,hfmnode[i].weight,hfmnode[i].parent,hfmnode[i].lchild,hfmnode[i].code);}start2=clock() ;//开始计时2fseek(ifp,0,SEEK_SET);//将ifp指针移到文件开头位置fwrite(&FileLength,4,1,ofp);//将FileLength写入目标文件的前4个字节的位置fseek(ofp,8,SEEK_SET);//再将目标文件指针ofp移到距文件开头8个字节位置codes[0]=0;//将编码信息写入目标文件while(!feof(ifp)){fread(&c,1,1,ifp);filelength++;for(i=0;i<n;i++)if(c==hfmnode[i].ch) break; //ch必须也为unsigned 型strcat(codes,hfmnode[i].code);while(strlen(codes)>=8){for(i=0;i<8;i++)//将codes的前8位01代码表示的字符存入c{if(codes[i]=='1')c=(c<<1)|1;else c=c<<1;}fwrite(&c,1,1,ofp); //将新的字符写入目标文件sumlength++;strcpy(codes,codes+8);//更新codes的值}if(filelength==FileLength) break;}//再将剩余的不足8位的01代码补全8位,继续写入if(strlen(codes)>0){strcat(codes,"00000000");for(i=0;i<8;i++){if(codes[i]=='1')c=(c<<1)|1;else c=c<<1;}fwrite(&c,1,1,ofp);sumlength++;}sumlength+=8;printf("编码区总长为:%ld个字节\n",sumlength-8);//将sumlength和n的值写入目标文件,为的是方便解压fseek(ofp,4,SEEK_SET);fwrite(&sumlength,4,1,ofp);//把sumlength写进目标文件的第5-8个字节里fseek(ofp,sumlength,SEEK_SET);fwrite(&n,4,1,ofp);//把叶子数n写进编码段后面的4个字节的位置//为方便解压,把编码信息存入n后面的位置//存储方式为:n*〔字符值〔1个字节〕+该字符的01编码的位数〔1个字节〕+编码〔字节数不确定,用count来计算总值〕〕for(i=0;i<n;i++){fwrite(&(hfmnode[i].ch),1,1,ofp);c=hfmnode[i].CodeLength;//编码最长为256位,因此只需用一个字节存储fwrite(&c,1,1,ofp);//写入字符的编码if(hfmnode[i].CodeLength%8!=0)for(j=hfmnode[i].CodeLength%8;j<8;j++)//把编码不足8位的在低位补0,赋值给C,再把C写入strcat(hfmnode[i].code,"0");while(hfmnode[i].code[0]!=0)//开始存入编码,每8位二进制数存入一个字节{c=0;for(j=0;j<8;j++){if(hfmnode[i].code[j]=='1')c=(c<<1)|1;else c=c<<1;}strcpy(hfmnode[i].code,hfmnode[i].code+8);//编码前移8位,继续存入编码count++; //编码占的字节数的总值fwrite(&c,1,1,ofp);}}printf("\n");finish2=clock();duration2=(double)(finish2- start2) / CLOCKS_PER_SEC;/*printf( "写入目标文件用时为:%f seconds\n", duration2);*/ printf( "压缩用时为:%f seconds\n", duration1+duration2);speed=(float)FileLength/(duration1+duration2)/1000;printf("\n压缩速率为:%5.2f KB/S\n",speed);printf("\n");printf("源文件长度为:%ld个字节\n",FileLength);sumlength=sumlength+4+n*2+count; //计算压缩后文件的长度printf("压缩后文件长度为:%ld个字节\n",sumlength);rate=(float)sumlength/(float)FileLength;printf("压缩率(百分比)为:%4.2f%%%\n",rate*100);fclose(ifp);fclose(ofp);return;}//返回书签//建立哈弗曼树中用于选择最小权值结点的函数int select(struct node *nodep,int pose){int i;int s1;long min=2147483647;//s初值为long型的最大值for(i=0;i<=pose;i++){if(nodep[i].parent!=-1)continue;if(nodep[i].weight<min){min=nodep[i].weight;s1=i;}}return s1;}//返回书签//哈弗曼编码函数void encode(struct node *nodep,int n){ //从叶子向根求每个字符的哈弗曼编码int start;int i,f,c;char codes[256];codes[n-1]='\0'; //编码结束符for(i=0;i<n;i++) //逐个字符求哈弗曼编码{start=n-1;for(c=i,f=nodep[i].parent;f!=-1;c=f,f=nodep[f].parent){start--;if(nodep[f].lchild==c)codes[start]='0';else codes[start]='1';}strcpy(nodep[i].code,&codes[start]);nodep[i].CodeLength=strlen(nodep[i].code);}}//返回书签//解压函数void uncompress() //解压文件{clock_t start, finish;double duration;FILE *ifp,*ofp;char infile[20],outfile[20];long FileLength,sumlength,filelength;int n,m;int i,j,k;char buf[256],codes[256];unsigned char c;int maxlength;float speed;printf("请输入要解压的文件名:");scanf("%s",infile);ifp=fopen(infile,"rb");if(ifp==NULL){printf("文件名输入错误,文件不存在!\n");return;}printf("请输入目标文件名:");scanf("%s",outfile);ofp=fopen(outfile,"wb");if(ofp==NULL){printf("文件名输入错误,文件不存在!\n");return;}start=clock() ;//开始计时fread(&FileLength,4,1,ifp);//从压缩文件读出FileLength、sumlengthfread(&sumlength,4,1,ifp);fseek(ifp,sumlength,SEEK_SET); //利用sumlength读出n的值fread(&n,4,1,ifp);printf("\n解码信息:源文件长度为%d个字节,字符种类n=%d\n",FileLength,n);for(i=0;i<n;i++)//读结点信息{fread(&hfmnode[i].ch,1,1,ifp);//字符fread(&c,1,1,ifp);//编码长度hfmnode[i].CodeLength=c;hfmnode[i].code[0]=0;if(hfmnode[i].CodeLength%8>0) m=hfmnode[i].CodeLength/8+1;//m为编码占的字节数else m=hfmnode[i].CodeLength/8;for(j=0;j<m;j++)//根据字节长度m读出编码{fread(&c,1,1,ifp);//此处c为01编码转换成的字符itoa(c,buf,2);//字符型编码转换成二进制型〔首位为1〕//如果编码不够8位,则说明缺少了8-k位0,因此应先在前面空缺位写0for(k=8;k>strlen(buf);k--){strcat(hfmnode[i].code,"0");}//再把二进制编码存进hfmnode.code中strcat(hfmnode[i].code,buf);}hfmnode[i].code[hfmnode[i].CodeLength]=0;//去掉编码中多余的0}//找出编码长度的最大值maxlength=0;for(i=0;i<n;i++)if(hfmnode[i].CodeLength>maxlength)maxlength=hfmnode[i].CodeLength;//开始写入目标文件fseek(ifp,8,SEEK_SET); //指针指向编码区,开始解码filelength=0;codes[0]=0;buf[0]=0;while(1){while(strlen(codes)<maxlength)//codes小于编码长度的最大值时,继续读码{fread(&c,1,1,ifp);itoa(c,buf,2);//还原编码for(k=8;k>strlen(buf);k--){strcat(codes,"0");//把缺掉的0补上}strcat(codes,buf);//codes中此时存的为一串01编码}for(i=0;i<n;i++){ //在codes中查找能使其前weight位和hfmnode.code相同的i值,weight 即为codelengthif(memcmp(hfmnode[i].code,codes,(unsignedint)hfmnode[i].CodeLength)==0) break;}strcpy(codes,codes+hfmnode[i].CodeLength);//更新codes的值c=hfmnode[i].ch;fwrite(&c,1,1,ofp);filelength++;if(filelength==FileLength) break;//写入结束}finish = clock();duration = (double)(finish - start) / CLOCKS_PER_SEC;printf( "\n解压完成,解压用时为:%f seconds\n", duration );fseek(ifp,0,SEEK_SET);FileLength=0;while(!feof(ifp)){fread(&c,1,1,ifp);FileLength++;}FileLength--;speed=(float)FileLength/duration/1000;/*printf("此文件长度为:%ld个字节\n",FileLength);*/printf("\n解压速度为:%5.2fKB/S\n",speed);fclose(ifp);fclose(ofp);return;}2.2程序运行结果:1.对文件〞测试.txt〞进行压缩,压缩后存储在文件〞目标.doc〞中,压缩速率为:2055.00KB/S,压缩率为64.92%。

哈夫曼编码的压缩与解压缩

哈夫曼编码的压缩与解压缩

哈夫曼编码的压缩与解压缩1.引言1.1 概述哈夫曼编码是一种常用的数据压缩算法,它采用了一种变长编码方式,将出现频率高的字符用较短的编码表示,出现频率低的字符用较长的编码表示,以达到压缩数据的目的。

该编码方法由美国数学家大卫·哈夫曼于1952年提出,被广泛应用于各种数据压缩和传输领域。

在传统的固定长度编码中,每个字符都使用相同的位数来表示,因此在表示不同概率出现的字符时,可能会浪费大量的位数。

而哈夫曼编码则是根据字符在文本中出现的频率来确定其对应的编码,使得高频出现的字符用更短的编码表示,低频出现的字符用较长的编码表示。

哈夫曼编码的核心思想是构建一棵哈夫曼树,将出现频率作为权值,频率越高的字符离根节点越近。

通过从叶子节点到根节点的路径确定每个字符的编码,即将左子树标记为0,右子树标记为1。

在对文本进行压缩时,将文本中的字符转换为其对应的哈夫曼编码,即可将原始数据压缩为较短的二进制串。

相比于传统固定长度编码,哈夫曼编码具有显著的优势。

首先,它可以根据文本中字符出现的实际情况进行编码,使得频率高的字符用较短的编码表示,从而大幅度减少了编码后的数据长度。

其次,哈夫曼编码是一种前缀编码,即任何一个字符的编码都不是其他字符编码的前缀,这样在解码时可以直接根据编码找到对应的字符,无需回溯查表,提高了解码效率。

哈夫曼编码的应用广泛,它被用于无损图像压缩、音频压缩、文本压缩等领域。

随着互联网和大数据时代的到来,数据的传输和存储成为重要的问题,如何高效地压缩和解压缩数据成为一个热门的研究方向。

哈夫曼编码作为一种简单而有效的压缩算法,具有广阔的应用前景。

然而,哈夫曼编码也存在一些问题,如编码时需要构建哈夫曼树,需要额外的空间和时间开销,对于小规模数据可能会影响压缩效率。

因此,在实际应用中,需要综合考虑数据规模和应用场景,选择合适的压缩算法。

1.2 文章结构本文主要介绍了哈夫曼编码的压缩与解压缩。

文章分为引言、正文和结论三个部分。

哈夫曼的文件压缩解压程序课件

哈夫曼的文件压缩解压程序课件
为了提高哈夫曼压缩算法的性能和压缩比,可以采用以 下优化措施
对数据进行预处理,例如去除冗余信息、进行数据格式 化等,以减少需要压缩的数据量。
使用更高效的编码和解码算法,例如使用位操作代替字 符串操作,减少内存占用和计算时间。
采用动态哈夫曼编码技术,根据数据流的特点动态调整 哈夫曼树的结构,以实现更好的压缩效果。
文件压缩原理
文件压缩的概念
01
02
03
文件压缩
通过特定的算法对文件进 行编码,以减少其存储空 间的需求,同时保持文件 完整性和可读性。
压缩文件
经过压缩处理后的文件, 其大小明显小于原始文件 。
解压缩
将压缩文件还原为原始大 小的过程。
文件压缩的原理
数据冗余
大多数文件都包含大量的 数据冗余,如重复的模式 或数据块。
编码
利用数据冗余,通过特定 的算法对数据进行编码, 以减少存储空间需求。
熵编码
根据数据的概率分布进行 编码,使常见数据使用较 短的编码,罕见数据使用 较长的编码。
常见文件压缩算法
Huffman编码
Bzip2
一种基于熵编码的算法,根据数据的 概率分布创建字符到二进制码的映射 。
使用Burrows-Wheeler变换和 Huffman编码的压缩算法,具有较高 的压缩比和较好的数据完整性。
因素。
06
哈夫曼压缩解压程序应用 实例
哈夫曼压缩解压程序的使用场景
1 2
数据存储
哈夫曼压缩解压程序常用于数据存储领域,能够 有效地减小数据文件的大小,节省存储空间。
网络传输
在网络传输中,哈夫曼压缩解压程序能够降低传 输时间和带宽消耗,提高传输效率。
3
实时处理
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

哈夫曼树的压缩与解压1.算法简要描述1.哈夫曼算法1.哈弗曼算法是根据给定的n个权值{w1,w2,w3.......wn},构造由n棵二叉树构成的深林F={T1,T2,。

Tn},其中每个二叉树Ti分别都是只含有一个权值wi的根结点,其左右子树为空(i=1,,,,,,2)。

2.在深林F中选取其根结点的权值最小的两棵二叉树,分别作其左右子树构造一颗新的二叉树,并置这棵新的二叉树根结点的权值为其左右子树的根结点之和。

3.从F中删去这两棵二叉树,同时刚新生成的二叉树加入到深林F中。

4.重复2,3,步骤,直至深林F中只含有一颗二叉树为止。

2.哈夫曼树的实现函数String EnCode(Char Type ch):表示哈夫曼树已存在,返回字符ch的编码。

函数LinkList<CharType>UnCode(String strCode):表示对哈夫曼树进行译码,返回编码前的字符序列。

根据算法可以看出,在具有n个结点权值的哈夫曼树的构造过程中,每次都是从F中删去两棵树,增加一棵树,即每次结束后减少一棵树,经过n-1次处理后,F中就只剩下一棵树了。

另外,每次合并都要产生一个新的结点,合并n-1次后共产生了n-1个新结点,并且这n-1个新节点都是具有左右子树的分支结点。

则最终得到的哈夫曼树中共有2n-1个结点,并且其中没有度为1的分支结点,最后一次产生的新结点就是哈夫曼树的根结点。

源代码中创建了一个哈夫曼树结点类,其中有数据成员weight,parent,leftChild,rightChild分别代表了权值,双亲,左孩子,右孩子。

在哈夫曼树类中有数据成员*nodes,*LeafChars,*LeafCharCodes,curPos,num,分别用来存储结点信息,叶结点字符信息,叶结点字符编码信息,译码时从根结点到叶结点路径的当前结点,叶结点个数。

哈夫曼树类中含有多个函数,有构造函数,析构函数等。

由函数HuffmanTree(CharType ch[],WeightType w[],int n)来构造由字符,权值,和字符个数构造哈夫曼树,在根据哈夫曼算法很容易实现哈夫曼类的函数以及构造函数。

在在算法中,求叶结点字符的编码时,需要从叶结点出发走一条从高叶结点到根结点的路径,而编码却是从根结点出发到叶结点的路径,由左分支为编码0,右分支为编码1,得到的编码,因此从叶结点出发到根结点的路径得到的编码是实际编码的逆序,并且编码长度不确定,又由于可以再线性链表中构造串,因此将编码的信息储存在一个线性立案标准,每得到一位编码都将其插入在线性链表的最前面。

在求某个字符的编码是由函数EnCode(CharType ch)来求,返回字符编码。

在进行译码时,用一个线性链表存储字符序列,由函数Decode(String strCode)来求,对编码串strCode进行译码,返回编码前的字符序列。

函数Compress()用哈夫曼编码压缩文件。

函数Decompress()解压缩用哈夫曼编码压缩的文件。

在主函数中有两个选项,一个是选择编码压缩,一个是解压。

在函数中使用了文件输入输出流,我们可以选择要压缩的文件名输入,在选出压缩文件保存的地方和文件类型,将压缩所得到的文件存储在另一个文件中,解压也是如此。

2.源代码#ifndef __HUFFMAN_TREE_NODE_H__#define __HUFFMAN_TREE_NODE_H__// 哈夫曼树结点类模板template <class WeightType>struct HuffmanTreeNode{WeightType weight; // 权数据域unsigned int parent, leftChild, rightChild; // 双亲,左右孩子域HuffmanTreeNode(); // 无参数的构造函数模板HuffmanTreeNode(WeightType w, int p = 0, int lChild = 0, int rChild = 0);// 已知权,双亲及左右孩子构造结构};// 哈夫曼树结点类模板的实现部分template <class WeightType>HuffmanTreeNode<WeightType>::HuffmanTreeNode()// 操作结果:构造空结点{parent = leftChild = rightChild = 0;}template <class WeightType>HuffmanTreeNode<WeightType>::HuffmanTreeNode(WeightType w, int p, int lChild, int rChild) // 操作结果:构造一个权为w,双亲为p,左孩子为lChild,右孩子为rChild的结点{weight = w; // 权parent = p; // 双亲leftChild = lChild; // 左孩子rightChild = rChild; // 右孩子}#endif#ifndef __HUFFMAN_TREE_H__#define __HUFFMAN_TREE_H__#include"string.h"// 串类#include"huffman_tree_node.h"// 哈夫曼树结点类模板// 哈夫曼树类模板template <class CharType, class WeightType>class HuffmanTree{protected:HuffmanTreeNode<WeightType> *nodes; // 存储结点信息,nodes[0]未用CharType *LeafChars; // 叶结点字符信息,LeafChars[0]未用String *LeafCharCodes; // 叶结点字符编码信息,LeafCharCodes[0]未用int curPos; // 译码时从根结点到叶结点路径的当前结点int num; // 叶结点个数// 辅助函数模板:void Select(int cur, int &r1, int &r2); // nodes[1 ~ cur]中选择双亲为,权值最小的两个结点r1,r2void CreatHuffmanTree(CharType ch[], WeightType w[], int n);// 由字符,权值和字符个数构造哈夫曼树public:// 哈夫曼树方法声明及重载编译系统默认方法声明:HuffmanTree(CharType ch[], WeightType w[], int n); // 由字符,权值和字符个数构造哈夫曼树virtual ~HuffmanTree(); // 析构函数模板String Encode(CharType ch); // 编码LinkList<CharType> Decode(String strCode); // 译码HuffmanTree(const HuffmanTree<CharType, WeightType> &copy); // 复制构造函数模板HuffmanTree<CharType, WeightType> &operator=(const HuffmanTree<CharType, WeightType>& copy); // 重载赋值运算符};// 孩子兄弟表示哈夫曼树类模板的实现部分template <class CharType, class WeightType>void HuffmanTree<CharType, WeightType>::Select(int cur, int &r1, int &r2)// 操作结果:nodes[1 ~ cur]中选择双亲为,权值最小的两个结点r1,r2{r1 = r2 = 0; // 0表示空结点for (int pos = 1; pos <= cur; pos++){ // 查找树值最小的两个结点if (nodes[pos].parent != 0) continue; // 只处理双亲不为的结点if (r1 == 0){r1 = pos; // r1为空,将pos赋值给r1}else if (r2 == 0){r2 = pos; // r2为空,将pos赋值给r2}else if(nodes[pos].weight < nodes[r1].weight){r1 = pos; // nodes[pos]权值比nodes[r1]更小,将pos赋为r1}else if (nodes[pos].weight < nodes[r2].weight){r2 = pos; // nodes[pos]权值比nodes[r2]更小,将pos赋为r2}}}void HuffmanTree<CharType, WeightType>::CreatHuffmanTree(CharType ch[], WeightType w[], int n)// 操作结果:由字符,权值和字符个数构造哈夫曼树{num = n; // 叶结点个数int m = 2 * n - 1; // 结点个数nodes = new HuffmanTreeNode<WeightType>[m + 1]; // nodes[0]未用LeafChars = new CharType[n + 1]; // LeafChars[0]未用LeafCharCodes = new String[n + 1]; // LeafCharCodes[0]未用int pos; // 临时变量for (pos = 1; pos <= n; pos++){ // 存储叶结点信息nodes[pos].weight = w[pos - 1]; // 权值LeafChars[pos] = ch[pos - 1]; // 字符}for (pos = n + 1; pos <= m; pos++){ // 建立哈夫曼树int r1, r2;Select(pos - 1, r1, r2); // nodes[1 ~ pos - 1]中选择双亲为,权值最小的两个结点r1,r2// 合并以r1,r2为根的树nodes[r1].parent = nodes[r2].parent = pos; // r1,r2双亲为posnodes[pos].leftChild = r1; // r1为pos的左孩子nodes[pos].rightChild = r2; // r2为pos的右孩子nodes[pos].weight = nodes[r1].weight + nodes[r2].weight;//pos的权为r1,r2的权值之和}for (pos = 1; pos <= n; pos++){ // 求n个叶结点字符的编码LinkList<char> charCode; // 暂存叶结点字符编码信息for (unsigned int child = pos, parent = nodes[child].parent; parent != 0;child = parent, parent = nodes[child].parent){ // 从叶结点到根结点逆向求编码if (nodes[parent].leftChild == child) charCode.Insert(1, '0');// 左分支编码为'0'else charCode.Insert(1, '1'); // 右分支编码为'1'}LeafCharCodes[pos] = charCode; // charCode中存储字符编码}curPos = m; // 译码时从根结点开始,m为根}HuffmanTree<CharType, WeightType>::HuffmanTree(CharType ch[], WeightType w[], int n) // 操作结果:由字符,权值和字符个数构造哈夫曼树{CreatHuffmanTree(ch, w, n); // 由字符,权值和字符个数构造哈夫曼树}template <class CharType, class WeightType>HuffmanTree<CharType, WeightType>::~HuffmanTree()// 操作结果:销毁哈夫曼树{if (nodes != NULL) delete []nodes; // 释放结点信息if (LeafChars != NULL) delete []LeafChars; // 释放叶结点字符信息if (LeafCharCodes != NULL) delete []LeafCharCodes; // 释放叶结点字符编码信息}template <class CharType, class WeightType>String HuffmanTree<CharType, WeightType>::Encode(CharType ch)// 操作结果:返回字符编码{for (int pos = 1; pos <= num; pos++){ // 查找字符的位置if (LeafChars[pos] == ch) return LeafCharCodes[pos];// 找到字符,得到编码}throw Error("非法字符,无法编码!"); // 抛出异常}template <class CharType, class WeightType>LinkList<CharType> HuffmanTree<CharType, WeightType>::Decode(String strCode)// 操作结果:对编码串strCode进行译码,返回编码前的字符序列{LinkList<CharType> charList; // 编码前的字符序列for (int pos = 0; pos < strCode.Length(); pos++){ // 处理每位编码if (strCode[pos] == '0') curPos = nodes[curPos].leftChild; // '0'表示左分支else curPos = nodes[curPos].rightChild; // '1'表示左分支if (nodes[curPos].leftChild == 0 && nodes[curPos].rightChild == 0){ // 译码时从根结点到叶结点路径的当前结点为叶结点charList.Insert(charList.Length() + 1, LeafChars[curPos]);curPos = 2 * num - 1; // curPos回归根结点}}return charList; // 返回编码前的字符序列}template <class CharType, class WeightType>HuffmanTree<CharType, WeightType>::HuffmanTree(const HuffmanTree<CharType, WeightType> &copy)// 操作结果:由哈夫曼树copy构造新哈夫曼树——复制构造函数模板{num = copy.num; // 叶结点个数curPos = copy.curPos; // 译码时从根结点到叶结点路径的当前结点int m = 2 * num - 1; // 结点总数nodes = new HuffmanTreeNode<WeightType>[m + 1]; // nodes[0]未用LeafChars = new CharType[num + 1]; // LeafChars[0]未用LeafCharCodes = new String[num + 1]; // LeafCharCodes[0]未用int pos; // 临时变量for (pos = 1; pos <= m; pos++){ // 复制结点信息nodes[pos] = copy.nodes[pos]; // 结点信息}for (pos = 1; pos <= num; pos++){ // 复制叶结点字符信息与叶结点字符编码信息LeafChars[pos] = copy.LeafChars[pos]; // 叶结点字符信息LeafCharCodes[pos] = copy.LeafCharCodes[pos];// 叶结点字符编码信息}}template <class CharType, class WeightType>HuffmanTree<CharType, WeightType> &HuffmanTree<CharType,WeightType>::operator=(const HuffmanTree<CharType, WeightType>& copy)// 操作结果:将哈夫曼树copy赋值给当前哈夫曼树——重载赋值运算符{if (&copy != this){if (nodes != NULL) delete []nodes; // 释放结点信息if (LeafChars != NULL) delete []LeafChars; // 释放叶结点字符信息if (LeafCharCodes != NULL) delete []LeafCharCodes; // 释放叶结点字符编码信息num = copy.num; // 叶结点个数curPos = copy.curPos; // 译码时从根结点到叶结点路径的当前结点int m = 2 * num - 1; // 结点总数nodes = new HuffmanTreeNode<WeightType>[m + 1]; // nodes[0]未用LeafChars = new CharType[num + 1]; // LeafChars[0]未用LeafCharCodes = new String[num + 1]; // LeafCharCodes[0]未用int pos; // 临时变量for (pos = 1; pos <= m; pos++){ // 复制结点信息nodes[pos] = copy.nodes[pos]; // 结点信息}for (pos = 1; pos <= num; pos++){ // 复制叶结点字符信息与叶结点字符编码信息LeafChars[pos] = copy.LeafChars[pos]; // 叶结点字符信息LeafCharCodes[pos] = copy.LeafCharCodes[pos];// 叶结点字符编码信息}}return *this;}#endif#ifndef __HUFFMAN_COMPRESS_H__#define __HUFFMAN_COMPRESS_H__#include"huffman_tree.h"// 哈夫曼树类struct BufferType// 字符缓存器{char ch; // 字符unsigned int bits; // 实际比特数};class HuffmanCompress// 哈夫曼压缩类{protected:HuffmanTree<char, unsigned long> *pHuffmanTree;FILE *infp,*outfp; // 输入/出文件BufferType buf; // 字符缓存void Write(unsigned int bit); // 向目标文件中写入一个比特void WriteToOutfp(); // 强行将字符缓存写入目标文件public:HuffmanCompress(); // 无参数的构造函数~HuffmanCompress(); // 析构函数void Compress(); // 压缩算法void Decompress(); // 解压缩算法HuffmanCompress(const HuffmanCompress &copy); // 复制构造函数HuffmanCompress &operator=(const HuffmanCompress& copy);// 赋值运算符};HuffmanCompress::HuffmanCompress(){pHuffmanTree = NULL; // 空哈夫曼树}HuffmanCompress::~HuffmanCompress(){if (pHuffmanTree != NULL)delete []pHuffmanTree; // 释放空间}void HuffmanCompress::Write(unsigned int bit) // 操作结果:向目标文件中写入一个比特{buf.bits++; // 缓存比特数自增buf.ch = (buf.ch << 1) | bit; // 将bit加入到缓存字符中if (buf.bits == 8){ // 缓存区已满,写入目标文件fputc(buf.ch, outfp); // 写入目标文件buf.bits = 0; // 初始化bitsbuf.ch = 0; // 初始化ch}}void HuffmanCompress::WriteToOutfp() // 操作结果:强行将字符缓存写入目标文件{unsigned int len = buf.bits; // 缓存实际比特数if (len > 0){ // 缓存非空, 将缓存的比特个数增加到, 自动写入目标文件for (unsigned int i = 0; i < 8 - len; i++) Write(0);}}void HuffmanCompress::Compress()// 操作结果:用哈夫曼编码压缩文件{char infName[256], outfName[256]; // 输入(源)/出(目标)文件名cout << "请输入源文件名(文件小于GB):"; // 被压缩文件小于GBcin >> infName; // 输入源文件名if ((infp = fopen(infName, "r+b")) == NULL)throw Error("打开源文件失败!"); // 抛出异常fgetc(infp); // 取出源文件第一个字符if (feof(infp))throw Error("空源文件!"); // 抛出异常cout << "请输入目标文件:";cin >> outfName;if ((outfp = fopen(outfName, "w+b")) == NULL)throw Error("打开目标文件失败!"); // 抛出异常cout << "正在处理,请稍候..." << endl;const unsigned long n = 256; // 字符个数char ch[n]; // 字符数组unsigned long w[n]; // 字符出现频度(权) unsigned long i, size = 0;char cha;for (i = 0; i < n; i++){ // 初始化ch[]和w[]ch[i] = (char)i; // 初始化ch[i]w[i] = 0; // 初始化w[i]}rewind(infp); // 使源文件指针指向文件开始处cha = fgetc(infp); // 取出源文件第一个字符while (!feof(infp)){ // 统计字符出现频度w[(unsigned char)cha]++; // 字符cha出现频度自加size++; // 文件大小自加cha=fgetc(infp); // 取出源文件下一个字符}if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间pHuffmanTree = new HuffmanTree<char, unsigned long>(ch, w, n); // 生成哈夫曼树rewind(outfp); // 使目标文件指针指向文件开始处fwrite(&size, sizeof(unsigned long), 1, outfp); // 向目标文件写入源文件大小for (i = 0; i < n; i++){ // 向目标文件写入字符出现频度fwrite(&w[i], sizeof(unsigned long), 1, outfp);}buf.bits = 0; // 初始化bitsbuf.ch = 0; // 初始化chrewind(infp); // 使源文件指针指向文件开始处cha = fgetc(infp); // 取出源文件的第一个字符while (!feof(infp)){ // 对源文件字符进行编码,并将编码写入目标文件String strTmp = pHuffmanTree->Encode(cha);// 字符编码for (i = 0; i<strTmp.Length(); i++){ // 向目标文件写入编码if (strTmp[i] == '0')Write(0); // 向目标文件写入else Write(1); // 向目标文件写入}cha = fgetc(infp); // 取出源文件的下一个字符}WriteToOutfp(); // 强行写入目标文件fclose(infp); fclose(outfp); // 关闭文件cout << "处理结束." << endl;}void HuffmanCompress::Decompress()// 操作结果:解压缩用哈夫曼编码压缩的文件{char infName[256], outfName[256]; // 输入(压缩)/出(目标)文件名cout << "请输入压缩文件名:";cin >> infName;if ((infp = fopen(infName, "r+b")) == NULL)throw Error("打开压缩文件失败!"); // 抛出异常fgetc(infp); // 取出压缩文件第一个字符if (feof(infp))throw Error("压缩文件为空!"); // 抛出异常cout << "请输入目标文件名:";cin >> outfName;if ((outfp = fopen(outfName, "w+b")) == NULL)throw Error("打开目标文件失败!"); // 抛出异常cout << "正在处理,请稍候..." << endl;const unsigned long n = 256; // 字符个数char ch[n]; // 字符数组unsigned long w[n]; // 权unsigned long i, size = 0;char cha;rewind(infp); // 使源文件指针指向文件开始处fread(&size, sizeof(unsigned long), 1, infp); // 读取目标文件的大小for (i = 0; i < n; i++){ch[i] = (char)i; // 初始化ch[i]fread(&w[i], sizeof(unsigned long), 1, infp); // 读取字符频度}if (pHuffmanTree != NULL)delete []pHuffmanTree; // 释放空间pHuffmanTree = new HuffmanTree<char, unsigned long>(ch, w, n); // 生成哈夫曼树unsigned long len = 0; // 解压的字符数cha = fgetc(infp); // 取出源文件的第一个字符while (!feof(infp)){ // 对压缩文件字符进行解码,并将解码的字符写入目标文件String strTmp = ""; // 将cha转换二进制形式的串unsigned char c = (unsigned char)cha; // 将cha转换成unsigned char类型for (i = 0; i < 8; i++){ // 将c转换成二进制串if (c < 128) Concat(strTmp, "0"); // 最高位为else Concat(strTmp, "1"); // 最高位为c = c << 1; // 左移一位}LinkList<char> lkText = pHuffmanTree->Decode(strTmp);// 译码String strTemp(lkText);for (i = 1; i<=strTemp.Length(); i++){ // 向目标文件写入字符len++; // 目标文件长度自加fputc(strTemp[i-1], outfp); // 将字符写入目标文件中if (len == size) break; // 解压完毕退出内循环}if (len == size) break; // 解压完毕退出外循环cha = fgetc(infp); // 取出源文件的下一个字符}fclose(infp); fclose(outfp); // 关闭文件cout << "处理结束." << endl;}HuffmanCompress::HuffmanCompress(const HuffmanCompress &copy)// 操作结果:由哈夫曼压缩类对象copy构造新哈夫曼压缩类对象——复制构造函数{if (copy.pHuffmanTree != NULL){ // copy为非空哈夫曼压缩类对象pHuffmanTree = new HuffmanTree<char, unsigned long>(*copy.pHuffmanTree);// 生成哈夫曼树}else pHuffmanTree = NULL; // 生成空哈夫曼压缩类对象}HuffmanCompress &HuffmanCompress::operator=(const HuffmanCompress& copy)// 操作结果:将哈夫曼压缩类对象copy赋值给当前哈夫曼压缩类对象——赋值运算符{if (&copy != this){if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间if (copy.pHuffmanTree != NULL){ // copy为非空哈夫曼压缩类对象pHuffmanTree = new HuffmanTree<char, unsigned long>(*copy.pHuffmanTree);// 生成哈夫曼树}else pHuffmanTree = NULL; // 生成空哈夫曼压缩类对象}return *this;}#endif#include"utility.h"// 实用程序软件包#include"huffman_compress.h"// 哈夫曼压缩算法int main(void){try// 用try封装可能出现异常的代码{HuffmanCompress obj;int select = 0;while (select != 3){cout << endl << "1. 压缩";cout << endl << "2. 解压";cout << endl << "3. 退出";cout << endl << "请选择:";cin >> select; // 输入选择while (cin.get() != '\n'); // 忽略用户输入的其他字符switch (select){case 1:press(); // 压缩break;case 2:obj.Decompress(); // 解压缩}}}catch (Error err) // 捕捉并处理异常{err.Show(); // 显示异常信息}system("PAUSE"); // 调用库函数system()return 0; // 返回值, 返回操作系统}3.测试结果由于测试的文件字符太多,则就截下来一部分的压缩后的编码。

相关文档
最新文档