1、算法名称
基于huffman编码的文件压缩与解压程序的实现。
3、运行环境说明
改程序在microsoft visual c++6.0 环境下开发完成
可执行文件可以脱离改开发环境在任何windows系统中运行
4、运行过程说明
改程序是基于huffman编码的文件压缩与解压程序,可以讲任何格式的文件压缩为.huff格式的文件,也只能解压有改程序压缩完成的文件,可以将文件解压问指定的格式。
文件运行过程如下:
(1)打开可执行程序后,选择操作的类型,1压缩文件,2解压文件,0退出程序
(2)选择后可根据提示,压缩文件时输入带格式的文件名称(绝对或相对路径名称,带文件格式),然后输入压缩后的文件名称即可。解压文件输入压缩文件名称(该文件是由该程序压缩完成的,不能输入文件格式,只要名称),然后输入解压后的文件名称(带格式即可)。
5、算法设计
该算法使用了单向有序链表和二叉树的数据结构,单向有序链表用于存储字符及其出现频率,其第一个和第二个节点即为第一小和第二小的节点,用两个节点构造新节点插入链表中,依次类推即可得到huffman二叉树。
Huffman树构造算法的设计如下:
Huffman(C)
1 n<—|C|
2 L<—C
3 for i <—1 to n-1
4 do allocate a new node z
5 left[z] <— x <— EXITRACT-MIN(L)
6 right[z] <— y <— EXITRACT-MIN(L)
7 f[z]=f[x]+f[y]
8 INSERT(L,z)
9 return EXTRACT(L); //返回根节点
huffman.h
//#include <vector> #include <iostream> #include <map> #include <bitset> #include <string> #include <cstring> #include <queue> #include <fstream> using namespace std; /* 本程序采用单向链表的数据结构来求解huffman树,使用到了带头结点的链表的插入、删除、排序等操作 */ typedef struct linkNode //单向链表的结构体 { struct treeNode *node; //huffman树上的节点 linkNode *next;//指向下一个节点的指针 }; struct treeNode //huffman树上节点的结构体 { unsigned char name; //节点名称,叶节点为对应的字符,内部节点为‘*’ treeNode *left;//指向它的左子树的指针 treeNode *right;//指向它的右子树的指针 double value;//节点的权值,也就是字符出现的频率值,本算法中使用器出现的次数,不受影响 string code;//节点的编码,以01字符串的形式出现 }; linkNode * new_linknode(char name,double value);//该函数用于新建一个链表上的节点,链表上的节点的结构如上所示 treeNode * new_treenode(char name,double value);//该函数用于新建一个huffman树上的节点 unsigned long char_account(ifstream &input,map<char,double> &vec_char);//统计文件中的字符 以及字符的个数,最终形成一个字符对应其出现次数的映射表 treeNode* get_huffmantree(map<char,double> &vec_char);//从一个字符与其出现次数的映射表得到哈夫曼树 ,返回的是树的节点 linkNode *get_tree_from_link(linkNode *head);//从一个权值升序的单向链表中得到一颗huffman树,最终返回的是包含huffman树根节点的链表节点 int get_encode_dictionary(treeNode *root,map<char,string> &vec_encode);//从一颗huffman树得到其叶节点对应的huffman编码 void linklist_insert(linkNode *head,linkNode *element);//在一个有序链表上插入一个新节点,所得到的链表依然有序 void string_to_binary(string str,bool array[128]);//把一个01字符串转换为对应的01布尔值存储在一个bool数组中 int compress(string input_filename,string encode_filename);//文件的压缩过程 int uncompress(string input_filename,string encode_filename);//文件的解压过程
huffman.cpp
#pragma warning(disable:4786) #include"huffman.h" #include <cstring> using namespace std; treeNode * new_treenode(char name,double value) { treeNode *newNode=new treeNode; newNode->name=name; newNode->left=NULL; newNode->right=NULL; newNode->value=value; newNode->code=""; return newNode; } linkNode * new_linknode(char name,double value) { linkNode *newnode=new linkNode; newnode->node=new_treenode(name,value); newnode->next=NULL; return newnode; } unsigned long char_account(ifstream &input,map<char,double> &vec_char) { unsigned long sizes=0; map<char,double>::iterator it; //定义map的迭代器 if(input==NULL)//输入文件流为空直接返回 return 0; unsigned char buf=0; input.read((char*)&buf,1);// 二进制读入一个字符 sizes++; while (!input.eof())//连续读入字符知道文件结束 { it=vec_char.find((char)buf); if (it==vec_char.end())//若字符尚未出现过则插入映射表,否则出现次数加一 vec_char.insert(map<char,double>::value_type((char)buf,1)); else it->second++; input.read((char*)&buf,1); sizes++; } input.close(); return sizes-1; } treeNode *get_huffmantree(map<char,double> &vec_char) { linkNode *temp=NULL; linkNode *head=new linkNode; head->node= NULL; head->next=NULL; map<char,double>::iterator it;//容器的迭代器 for(it=vec_char.begin();it!=vec_char.end();it++) { temp=new_linknode(it->first,it->second); linklist_insert(head,temp); } // 此时的head为什么是空 treeNode *root; root=get_tree_from_link(head)->next->node; return root; } linkNode *get_tree_from_link(linkNode *head) { if (head->next==NULL) //链表中没有节点 { return NULL; } if (head->next->next==NULL) //链表中只有一个节点 { return head; } linkNode *first_min=head->next; //获得最小节点 linkNode *second_min=head->next->next;//获得次小节点 head->next=second_min->next;//删除前两个节点 linkNode *newnode=new_linknode('*',first_min->node->value+second_min->node->value);//由前两个各节点构造新节点 newnode->node->left=first_min->node;//添加新节点的左子树 newnode->node->right=second_min->node;//添加新节点的右子树 linklist_insert(head,newnode);//链表中插入新节点 get_tree_from_link(head);//递归调用,直到只剩一个根节点然后退出 return head; } void linklist_insert(linkNode *head,linkNode *element)//使用带头结点的单向链表,将一个节点插入有序链表 { if (element==NULL) { return; } if (head->next==NULL) { head->next=element; element->next=NULL; return; } linkNode *p=head->next; //工作指针,指向第一个节点 linkNode *q=head;//记录插入点 while (p) { if (p->node->value < element->node->value) { q=p; p=p->next; }else{ element->next=p; q->next=element; break; } } if(p==NULL) q->next=element; } void string_to_binary(string str,bool array[256]) { int len=str.length(); int i; for (i=0;i<len;i++)//将01字符串中的字符分别转换成bool值 { if (str.at(i)=='0') { array[i]=0; }else{ array[i]=1; } } } int get_encode_dictionary(treeNode *root,map<char,string> &vec_encode) { static bool flag=false; if(root==NULL) { return 0; } if(root->left==NULL&&root->right==NULL){ //该节点是叶节点 vec_encode.insert(map<char,string>::value_type(root->name,root->code)); } else{//节点是内部节点 flag=true;//进入该循环说明不是只有一个节点huffman树 root->left->code=root->code+"0"; root->right->code=root->code+"1"; get_encode_dictionary(root->left,vec_encode);//左子树递归调用 get_encode_dictionary(root->right,vec_encode);//右子树递归调用 } if(!flag) //文件中只有一种类型的字符串; vec_encode.begin()->second='0'; return 1; } int compress(string input_filename,string encode_filename)//文件的压缩过程 { map<char,double> ch_count; map<char,string> encode_dictionary; unsigned long sizes=0; ifstream input(input_filename.c_str(),ios::in|ios::binary);//二进制打开输入文件 if (!input) { cout<<"can not open the input file"<<endl; return 0; } ofstream output(encode_filename.c_str(),ios::out|ios::binary);//二进制打开输出文件 if (!output) { cout<<"can not open the output file"<<endl; return 0; } sizes=char_account(input,ch_count);//统计字符 input.close(); input.clear(); get_encode_dictionary(get_huffmantree(ch_count),encode_dictionary);//获得用于压缩的huffman编码 ////////////////////////////////////////////////////////////////////////////////////////////////////////// //在压缩文件中写入解码信息 output.write((char*)&sizes,4);//写入原始文件的大小 unsigned char num=encode_dictionary.size(); output.write((char*)&num,1); //写入编码的个数,也就是huffman树的长度 int i=0; int bitIndex=0; string str; int len; char temp; bool array[256]; unsigned char buf=0; map<char,string>::iterator it; for (it=encode_dictionary.begin();it!=encode_dictionary.end();it++) { str=it->second; len=str.length(); output.write((char*)&(it->first),1);// 写入字符的键值 temp=(char)len; output.write((char*)&temp,1); //写入编码的长度 string_to_binary(str,array); for (i=0;i<len;i++) //write in the code of each char { buf>>=1; if (array[i]==1)//从array[0]到array[len]一位一位的写入buffer { buf|=0x80; } bitIndex++; if (bitIndex==8) //够八位就写入 { bitIndex=0; output.write((char*)&buf,1);//buf的内容写入ouput文件流 buf=0; //buf清空 } } if (bitIndex)//最后一个位写完,但是不够一个整八位 { buf>>=8-bitIndex; output.write((char*)&buf,1); bitIndex=0; buf=0; } } //////////////////////////////////////////////////////////////////////////////////////////////////////// input.open(input_filename.c_str(),ios::in|ios::binary);//重新打开输入文件 unsigned char ch; bitIndex=0; input.read((char*)&ch,1);//读取第一个字符 while (!input.eof()) { it=encode_dictionary.find((char)ch);//获取字符的huffman编码 str=it->second; string_to_binary(str,array); for (i=0;i<str.length();i++)//将huffman编码按位一位一位的写入压缩文件 { buf>>=1; if(array[i]) buf|=0x80; bitIndex++; if (bitIndex==8)//huffman编码凑够八位就按照一个字符写入压缩文件 { bitIndex=0; output.write((char*)&buf,1); buf=0; } } input.read((char*)&ch,1); } if (bitIndex)//同理 写完不够八位则右移到最低位 { buf >>= (8 - bitIndex); output.write((char*)&buf, 1); } input.close(); output.close(); return 1; } int uncompress(string input_filename,string encode_filename)//文件的解压过程 { ifstream input(input_filename.c_str(),ios::in|ios::binary); if (!input) { cout<<"can not open the input file"<<endl; return 0; } ofstream output(encode_filename.c_str(),ios::out|ios::binary); if (!output) { cout<<"can not open the output file"<<endl; return 0; } //首先从压缩文件的其实字符串得到文件的解码词典 之后便可根据解码词典进行解码 map<string,char> decode_dictionary;//定义解码字典 unsigned long sizes=0;//用于写入文件的长度 int num=0; //解码字典的长度 unsigned char num1=0; unsigned char len=0; char ch; unsigned char buf=0; string str=""; int bitIndex=0; bool array[256]; int i=0; int j=0; input.read((char*)&sizes,4); input.read((char*)&num1,1);// if (num1==0)//文件中的字符最多256个超出八个bit的表示能力 会表示问八个零 { num=256; }else{ num=num1; } for (i=0;i<num;i++) { input.read((char*)&ch,1); //读入解码字典的字符 input.read((char*)&len,1); //读入当前字符编码的编码长度 input.read((char*)&buf,1); for (j=0;j<len;j++) //根据长度读出后续的编码01串 { array[j]=buf&0x01; buf>>=1; bitIndex++; if(bitIndex==8)//读入的八个字符已经全部处理完 { bitIndex=0; if(j!=(len-1))//敲好是该编码的最后一个位,则处理完毕 input.read((char*)&buf,1); } } for (j=0;j<len;j++)//由01问构造字符串 { if (array[j]==1) str+='1'; else str+='0'; } decode_dictionary.insert(map<string,char>::value_type(str,ch));//将获得的字符对应的编码插入解码器 str=""; bitIndex=0; } //根据获得的解码器对文件进行解码 buf=0; i=0; bitIndex=0; str=""; bool flag=false; buf=0; char ch_test=0; input.read((char*)&buf,1);//二进制读入压缩文件中的一个字符 map<string,char>::iterator it; while(!input.eof()){ // 若文件没有结束则按照二进制一个字符一个字符的读入所有内容 for (i=0;i<256;i++) { array[i]=buf&0x01; if (array[i]==1) { str=str+'1'; }else{ str=str+'0'; } if ((it=decode_dictionary.find(str)) != decode_dictionary.end())//前缀编码中,根据编码找到对应的字符,将字符写入解压文件 { ch_test=it->second; output.write((char*)&(it->second),1); sizes--; if(sizes==0) return 1; str=""; } buf>>=1; bitIndex++; if(bitIndex==8)//读入的字符处理完毕,继续读入一个字符 { bitIndex=0; input.read((char*)&buf, 1); } } } return 1; }