基于哈夫曼编码的文件压缩


…当初上数据结构课程的时候写的程序,忘了发了。现在记不清一些细节上的东西了。决定就只把代码贴上来了,心得什么的已经没了,比如踩过的坑什么的…
代码里也有一些关键注释。
先上代码

#include
#include
#include
#include
#include
#include
#include
using namespace std;
int ShowHelp(){
    cout<<"please input the instructions,Z means zip the sourcefile,X means extract files,for example:'huftest Z a.txt a.huf'"<<endl;
    return 0;
}
struct Node
{
    // char massage;
    long long int index;                      //用于存字符对应ASCII码
    long long int weight;
    long long int parent,lcd,rcd;
};
struct hufftree
{
    Node *nodes;
    long long int leafnum;
};
struct BitOStream
{
    ostream *ostr;      // 指向目标输出流实例
    int bytebuffer;     // 字节缓冲,准备写入流的位要先写到字节缓冲中,当字节缓冲填满8个bit后,才将一个字节写入目标流中
    int bitmask;        // 位写入屏蔽字,准备写入bytebuffer的对应位为1,其他位为0
};
//顺序查找避免sort的不稳定
int creathufftree(hufftree &T,int leafnum,long long int *nodeweight)
{
    T.nodes=new Node[2*leafnum-1];
    T.leafnum=leafnum;
    for(int i=0;i<2*leafnum-1;i++)                          //初始化
    {
        T.nodes[i].index=i;
        if(i<leafnum)
        T.nodes[i].weight=nodeweight[i];
        else T.nodes[i].weight=0;
        T.nodes[i].parent=T.nodes[i].lcd=T.nodes[i].rcd=-1;
        // T.nodes[i].massage=char(T.nodes[i].weight);
    }
    for(int i=0;i<leafnum-1;i++){
        int k1,k2;
        int t1,t2;
        t1=t2=-1;
        for(int j=0;j<leafnum+i;j++){
            if(T.nodes[j].parent==-1)
            {
                if(t1==-1||T.nodes[j].weight<T.nodes[t1].weight){
                    t2=t1;          //t2需要更新到t1的位置
                    t1=j;
                }
                else if(t2==-1||T.nodes[j].weight<T.nodes[t2].weight){
                    t2=j;
                }
            }
            else{
                continue;       //parent!=-1则已经遍历过
            }
        }
        k1=min(t1,t2);
        k2=max(t1,t2);
        // k1=t1;k2=t2;
        //将挑选出来的两个节点作为左右子放进新节点T.nodes[leafnum+i]中
        T.nodes[leafnum+i].weight=T.nodes[k1].weight+T.nodes[k2].weight;
        T.nodes[leafnum+i].parent=-1;
        T.nodes[leafnum+i].lcd=k1;
        T.nodes[leafnum+i].rcd=k2;
        T.nodes[k1].parent=leafnum+i;
        T.nodes[k2].parent=leafnum+i;
    }
    return 0;
}
int Put_Bit(BitOStream &Byte_str, int bit){
    if(bit) Byte_str.bytebuffer |= Byte_str.bitmask;//将1存入buffer中
    Byte_str.bitmask>>=1;        //屏蔽字右移
    if(Byte_str.bitmask==0)     //右移八位,已经凑满一字节,写入输出流
    {
        Byte_str.ostr->put(Byte_str.bytebuffer);
        Byte_str.bitmask=0x80;
        Byte_str.bytebuffer=0;
    }
    return 0;
}
int cal(int mask){
	int i;
	for(i=0;i<8;i++)
	{
		if(pow(2,i)==mask)
		return i;
	}
	return 0;
}
int Finish_Put(BitOStream &Byte_str){
    int res=0;
    if(Byte_str.bitmask!=0x80)              //还有剩余未满八位的编码
    {
        res=7-cal(Byte_str.bitmask);          //最后一字节的有效编码位数,mask右移n位,就有n位有效
        Byte_str.ostr->put(Byte_str.bytebuffer);
        //重新初始化
        Byte_str.bitmask=0x80;
        Byte_str.bytebuffer=0;
    }
    return res;
}
//字符编码
vector<string> huffcode(hufftree &T){
    vector<string> Zipcodes(T.leafnum);     //256种字符
    char *str=new char[2*T.leafnum]{};      //考虑树深度最大可能超过256
    for(long long int i= 0 ; i < T.leafnum ; i++){
        long long int k,s=2*T.leafnum-2;              //由于从节点往根遍历,故逆序存入str中
        for(long long int j=i,k=T.nodes[i].parent;k!=-1;j=k,k=T.nodes[k].parent)
        str[s--]=(j==T.nodes[k].lcd ? '0' : '1');
        //找到根后退出循环
        Zipcodes[T.nodes[i].index]=str+s+1;
    }
    delete []str;
    return Zipcodes;
}
int destroy_tree(hufftree &T){
    delete []T.nodes;
    T.nodes=NULL;
    T.leafnum=0;
    return 0;
}
int huffmanZIP(long long int *weights,ifstream &instr,ofstream &outstr)
{
    hufftree T;
    //构建哈夫曼树
    creathufftree(T,256,weights);
    //构建哈夫曼编码
    vector<string> Zipcodes=huffcode(T);
    
    //将树存入压缩文件(编码头)
    int *codehead=new int[2*T.leafnum-1];           //2*leafnum-1(511)个节点
    for(int i=0; i<2*T.leafnum-1; i++)              //遍历每个节点
    codehead[i]=T.nodes[i].parent;                  //i的值等于index的值
   
   //保存父链
    for(int i=0; i<2*T.leafnum-1; i++)
    outstr.put(codehead[i]-255);                    //parent范围256-510大于1字节
    outstr.put(0);                                  //预留一个字节

    //根据编码将源文件对应编码位流写入压缩文件
    BitOStream Byte_str;
    //初始化
    Byte_str.bitmask=0x80;//十进制128,二进制10000000
    Byte_str.ostr=&outstr;
    Byte_str.bytebuffer=0;  
    //读取内容
    char ch;
    int rest;
    while(instr.get(ch)){
        string Acode=Zipcodes[(int)(unsigned char)(ch)];
        for(char c:Acode){          //遍历string,将1写入buffer
            Put_Bit(Byte_str,(c=='1'));
        }   
    }
    rest=Finish_Put(Byte_str);//最后一个字符遍历完后检查是否有不满8位的情况
    outstr.seekp(511,ios::beg);
    outstr.put(rest);
    //完成压缩,返回主函数关闭文档流    
    destroy_tree(T);
    delete []codehead;
    return 0;
}
int creatcodetree(hufftree &codeT,int *codehufftree){
    codeT.leafnum=256;
    codeT.nodes=new Node[511];
    for(int i=0;i<511;i++){         //初始化
        codeT.nodes[i].index=i;     //0-255 index就是字符ASCII转化的int值
        codeT.nodes[i].parent=codehufftree[i];      
        codeT.nodes[i].lcd=codeT.nodes[i].rcd=-1;
    }
    for(int i=0;i<511;i++)
    {
        if(codeT.nodes[codehufftree[i]].lcd!=-1&&codeT.nodes[codehufftree[i]].rcd!=-1)
        continue;
        if(codeT.nodes[codehufftree[i]].lcd==-1)    //先出现的是左子
        codeT.nodes[codehufftree[i]].lcd=i;
        else codeT.nodes[codehufftree[i]].rcd=i;
    }
    return 0;
}
int HuffmanExtract(ifstream &instr,ofstream &outstr){
    int codehufftree[511];
    for(int i=0;i<511;i++)          //前511字节是编码头,需将其还原成原哈夫曼树
    {
        char ch;
        instr.get(ch);
        codehufftree[i]=(int)(unsigned char)ch+255;
    }
    //-1是根节点的parent,而-1经过unsigned char转换后是255,即根节点parent=255
    codehufftree[510]=-1;
    char ch;
    instr.get(ch);
    int rest=(int)(unsigned char)ch;    //rest!=0则表示还有不满八位的有效编码

    hufftree codeT; 
    int root=510;               //根节点是下标510的节点
    creatcodetree(codeT,codehufftree);

    //读取二进制编码,根据编码遍历树得到对应字符,并写入输出流      
    //输入流获取单字节,转化为01序列

    instr.get(ch);
    int j=(int)(unsigned char)ch;
    queue <int> code;
    for(int i=0x80;i>0;i>>=1)       //第一个字符预处理
    code.push(j&i);
    int k=root;
    while(instr.get(ch)){
    	int flag=8; 
        j=(int)(unsigned char)ch;
        for(int i=0x80;i>0;i>>=1)       //一次8个编码入队
        code.push(j&i);
        while(flag--){
        	if(code.front())
            k=codeT.nodes[k].rcd;
            else k=codeT.nodes[k].lcd;
			code.pop();
            if(codeT.nodes[k].lcd==-1&&codeT.nodes[k].rcd==-1)
            {
                outstr.put(codeT.nodes[k].index);
                k=root;                 //k重新指向根
            }
        }
    }
    if(rest==0) rest=8;
    //还剩余rest有效编码未处理
    while(rest--){
            if(code.front())
            k=codeT.nodes[k].rcd;
            else 
            k=codeT.nodes[k].lcd;
            code.pop();
			if(codeT.nodes[k].lcd==-1&&codeT.nodes[k].rcd==-1)
            {
                outstr.put(codeT.nodes[k].index);
                k=root;                 //k重新指向根
            }
    }  
    return 0;
}
int main(int argc, char *argv[])
{
    long long int weights[256]={0};
    if(argc!=4)
    ShowHelp();
    else{
        if (stricmp(argv[1],"Z")==0)
        {
            cout << "ZIP  files..." << endl;
            // 打开输入文件流
            ifstream instr(argv[2], ios::in | ios::binary);//ios::binary--不对\n进行转码
            if (!instr)
            {
            cerr << "Open " << argv[2] << " failure." << endl;
            return 0;
            }
                // 从输入文件流逐字节读入,统计频率
            char ch;
            ofstream outstr(argv[3], ios::out | ios::binary);

            // while(ch=instr.get()&&instr.eof())//eof()到达文件末返回值是流,instr(ch)读到文件未返回值为int型
            while (instr.get(ch))
            {
                weights[(int)(unsigned char)ch]++;
            }
            instr.clear();                           //读到了EOF,clear清除
            instr.seekg(0,ios::beg);                 //指针重定位到0
            huffmanZIP(weights,instr,outstr);
            //调用函数压缩
            //关闭文档流
            outstr.close();
            instr.close();
        }
        if(stricmp(argv[1],"X")==0)
        {
            cout<<"extract files..."<<endl;
            //调用函数解压
            ifstream instr(argv[2], ios::in | ios::binary);
            ofstream outstr(argv[3],ios::out | ios::binary);
            HuffmanExtract(instr,outstr);
            instr.close();
            outstr.close();
        }
    }
    return 0;
}

具体思路就是利用哈夫曼算法,将文件以字节为单位,统计频率后重新编码,并将编码头写入压缩文件中,以达到压缩文件的目的,解压文件时先取出编码头中的哈夫曼树,再逐位取出压缩文件中二进制数,通过哈夫曼树解压出对应字符,写入解压文件中,以此实现解压操作。


测试情况:

(一) 程序概况:
大小:269 KB (275,816 字节);
占用空间:272 KB (278,528 字节);
(二) 压缩阈值:
512字节;(当需要压缩的文件大小小于512字节时压缩后的文件大小会大于源文件,某些情况下压缩后的文件大小可能会大于源文件,文件越小,压缩效果越差)
(三) 文件压缩情况:

  1. 小型文件(1):
  1. 源文件大小:4.62 MB (4,851,392 字节);
  2. 占用空间:4.62 MB (4,853,760 字节);
  3. 文件格式:exe;
  4. 耗时:小于1s;(解压耗时相当)
  5. 压缩后的文件大小:3.35 MB (3,517,957 字节),
    占用空间:3.35 MB (3,518,464 字节);
  6. 压缩率72.5%;
  7. 解压后文件大小及占用空间情况与源文件一致,并且能够正常使用
  1. 小型文件(2):
  1. 源文件大小:102 KB (104,982 字节)
  2. 占用空间:104 KB (106,496 字节);
  3. 文件格式:jpg;
  4. 耗时:小于1s;(解压耗时相当)
  5. 压缩后的文件大小:102 KB (105,374 字节),
    占用空间:104 KB (106,496 字节);
  6. 压缩率100.4%;
  7. 解压后文件大小及占用空间情况与源文件一致,并且能够正常使用
  1. 小型文件(3):
  1. 源文件大小:1.34 KB (1,377 字节);
  2. 占用空间:4.00 KB (4,096 字节);
  3. 文件格式:txt;
  4. 耗时:小于1s;(解压耗时相当)
  5. 压缩后的文件大小:1.53 KB (1,570 字节),
    占用空间:4.00 KB (4,096 字节);
  6. 压缩率114.0%;
  7. 解压后文件大小及占用空间情况与源文件一致,并且能够正常使用
  1. 大型文件1:
    1)源文件大小:2.60 GB (2,794,231,987 字节);
  1. 占用空间:2.60 GB (2,794,233,856 字节);
  2. 文件格式:mp4;
  3. 耗时:约4分7秒;(解压耗时6分40s)
  4. 压缩后的文件大小:1.13 GB (1,223,179,223 字节)、占用空间:1.13 GB (1,223,180,288 字节);
  5. 压缩率43.5%;
  6. 解压后文件大小及占用空间情况与源文件一致,并且能够正常使用;

(四) 问题分析:
程序压缩以及解压大型文件时耗时较久,并且,由于存有512字节的编码信息,小型文件在压缩后的文件大小极有可能超过源文件,压缩、解压过程中CPU占用率较高,约20%,操作大型文件内存占用较大。


程序使用说明:
使用方法:

  1. 点击电脑搜索按钮,输入cmd,回车;
    基于哈夫曼编码的文件压缩_第1张图片

  2. 进入命令行后,根据程序对应位置进入cd命令进入文件夹,此处以程序放在桌面为例:输入:cd Desktop并回车,输入命令:程序名 操作(X/Z) 源文件名 目标文件名,如:HuffmanAlgorithm_FilesZIP z thinkphp作业讲解.mp4 change.huff,其中z表示压缩,x表示解压(不区分大小写),程序运行结束后得到压缩文件change.huff;再次输入命令:HuffmanAlgorithm_FilesZIP x change.huff think.mp4得到解压后的文件.
    基于哈夫曼编码的文件压缩_第2张图片
    基于哈夫曼编码的文件压缩_第3张图片基于哈夫曼编码的文件压缩_第4张图片

    基于哈夫曼编码的文件压缩_第5张图片

你可能感兴趣的:(C++开发,c++,visual,studio,数据结构)