使用哈夫曼编码实现文件压缩__win10,c++

系统:win10

工具:vc6.0

//我加了个计时,用int存储字符总数

//因此增加一个限制:文件不可大于2GB

#include<iostream>
#include<time.h>

#define ASCIIL 256
#define F(x) ((x-1)>>1)
#define L(x) ((x<<1)+1)
#define R(x) ((x<<1)+2)
#define SWAP(a,b,tmp) {tmp=a;a=b;b=tmp;}

using namespace std;

int soufail_charcount;

//实现二叉堆模板类,小顶堆
template <class HeapType>
class CHeap
{
    HeapType *data,tmp;
    int size;
    
    void HeapUp(int ix)
    {//自底向顶维护堆
        int f;
        for(f=F(ix);ix&&data[ix]<data[f];ix=f,f=F(f))
            SWAP(data[ix],data[f],tmp);
    }
    
    void HeapDown(int ix)
    {//自顶向底维护堆
        int l,r,t;
        HeapType min,tmp;
        if(ix>=size) return ;
        l=L(ix),r=R(ix);
        
        min=data[ix],t=ix;
        
        if(l<size&&data[l]<min)
            t=l,min=data[l];
        if(r<size&&data[r]<min)
            t=r,min=data[l];
        
        SWAP(data[ix],data[t],tmp);
        if(ix!=t) HeapDown(t);
    }
    
public:
    CHeap()
    {//这里特殊应用,堆内元素个数不超过256
        size=0;
        data=new HeapType[256];
    }
    ~CHeap()
    {//释放内存
        delete [] data;

扫描二维码关注公众号,回复: 11552562 查看本文章

    }
    int getsize()
    {//返回堆大小
        return size;
    }
    void clear()
    {//清空堆
        size=0;
    }
    void insert(HeapType e)
    {//向堆尾中插入元素,并向顶维护堆
        data[size++]=e;
        HeapUp(size-1);
    }
    HeapType top()
    {//从堆顶取出元素,并向底维护堆
        HeapType ret=data[0];
        data[0]=data[--size];
        HeapDown(0);return ret;
    }
};

//哈夫曼树结点结构体实现
typedef struct talNode{
    
    unsigned char c;    //叶结点时对应ASCII值
    int weight;            //该结点权值
    int lt,rt;            //左、右结点下标
    
    talNode(){}
    
    talNode(unsigned char _c,int _p):
    c(_c),weight(_p),lt(-1),rt(-1)
    {}
    
    talNode(unsigned char _c,int _p,int l,int r)
        :c(_c),weight(_p),lt(l),rt(r)
    {}
    
    bool operator < (talNode a)
    {//重载运算符"<"用于二叉堆内的比较
        return weight<a.weight;
    }
    
}HuffNode;


//哈夫曼文件压缩类声明


class CHuffMan{
    HuffNode arr[512];        //哈夫曼树结点数组
    int size;                //哈夫曼树结点个数
    bool code[256][64];        //ASCII对应编码方案
    int lenth[256];            //ASCII对应编码长度

    //lastcodelenth,ps[256],用于存储压缩文件中作为文件头
    int lastcodelenth;        //文件最后一个字符实用几位
    int ps[256];            //ASCII对应出现频率
    int soucelen,targetlen; //源及目标文件长度


    //私有成员函数,用于实现内部功能
    void SetHuffTree(int []);            //根据字符频率生成哈夫曼树
    void SetHuffCode(int ,bool [],int );//根据哈夫曼树生成编码方案
    void CreateHuff(int []);            //创建哈夫曼树及编码方案
    void EnCodePre(char []);            //压缩前预处理
    void DeCodePre(FILE *);                //解压前预处理
    void SaveHuffHead(FILE *);            //保存压缩文件头
    void ReadHuffHead(FILE *);            //读取压缩文件头

public:
    CHuffMan(){Clear();}                //构造函数
    ~CHuffMan(){}                        //析构函数
    
    //公有成员函数,用于提供使用接口
    void Clear();                        //清空当前对象内容
    void EnCodeFile(char [],char []);    //编码,用于压缩文件,第一个参数为
                                        //源文件名,第二个参数为目标文件名
    void DeCodeFile(char [],char []);    //解码,用于解压文件,第一个参数为
                                        //源文件名,第二个参数为目标文件名
    void GetHuffCode();                    //输出ASCII对应编码方案
    int GetSouceLen();                    //输出当前工作项的源文件长度
    int GetTargetLen();                    //输出当前工作项的目标文件长度
    
};


void CHuffMan::SetHuffTree(int ps[])
{                                        //每次取出两权值最小的结点合并成新树,
                                        //加入堆,直至堆中只余有一个元素

    CHeap<HuffNode> hp;                    //二叉堆对象
    
    for(int i=0;i<ASCIIL;i++){            //如果字符i出现过,则插入二插堆
        if(ps[i])
            hp.insert(HuffNode(i,ps[i]));
    }
    size=0;                                //初始化哈夫曼树中结点个数
    while(hp.getsize()>1){
        arr[size++]=hp.top();            //取出权值最小的两个结点
        arr[size++]=hp.top();
        hp.insert(HuffNode(0,
            arr[size-1].weight+arr[size-2].weight,
            size-1,size-2));            //合并结点,并插入堆
    }
    arr[size++]=hp.top();                //arr[size-1]为哈夫曼树根
}

void CHuffMan::SetHuffCode(int ix,bool stk[],int top)
{                                        //递归深搜哈夫曼树,生成所有存在的ASCII
                                        //的前缀码
    if(arr[ix].c){                        //如果
        if(top){                        //此判断用于只含有一类字符的文件
            memmove(code[arr[ix].c],stk,sizeof(bool)*top);
            lenth[arr[ix].c]=top;
        }
        else lenth[arr[ix].c]=1;
        return ;
    }
    stk[top]=0;                            //左子树的边设为0
    SetHuffCode(arr[ix].lt,stk,top+1);    //递归进入左子树
    stk[top]=1;                            //右子树的边设为1
    SetHuffCode(arr[ix].rt,stk,top+1);    //递归进入右子树
}

void CHuffMan::CreateHuff(int ps[])
{                                        //构造哈夫曼树及前缀码
    bool stk[64];
    SetHuffTree(ps);                    //根据字符频率构造哈夫曼树
    SetHuffCode(size-1,stk,0);            //根据哈夫曼树生成前缀码
}
void CHuffMan::EnCodePre(char sfilename[])
{                                        //压缩文件预处理,读取字符出现频率
    FILE *fp;                            //及构造哈夫曼树及前缀码
    int c;
    fp=fopen(sfilename,"rb");
    if(fp==NULL){ 
        cout<<"读取文件错误"<<endl;
        exit(0);
    }
    memset(ps,0,sizeof(ps));            //读取字符出现频率
    while(true){
        c=fgetc(fp);
        if(feof(fp))break;
        ps[c]++;
    }
    fclose(fp);
    CreateHuff(ps);                        //构造哈夫曼树及前缀码
}

void CHuffMan::DeCodePre(FILE *fp)
{                                        //解压文件预处理,读取压缩文件头
                                        //根据读取头信息构千哈夫曼树及前缀码
    ReadHuffHead(fp);
    CreateHuff(ps);
}

void CHuffMan::SaveHuffHead(FILE *fp)
{                                            //向压缩文件中写文件头
    fwrite((void *)&lastcodelenth,4,257,fp);//从lastcodelenth的地址开始的连续
                                            //4*257个字节,即lastcodelenth和
                                            //ps[256]数组内容
    targetlen+=4*257;
}

void CHuffMan::ReadHuffHead(FILE *fp)
{                                        //从缩文件中读文件头
    fread((void *)&lastcodelenth,4,257,fp);    //从lastcodelenth的地址开始的连续
                                            //4*257个字节,即lastcodelenth和
    soucelen+=4*257;                        //ps[256]数组内容
}

void CHuffMan::Clear()
{                                            //清空前前工作项
    size=0;soucelen=targetlen=0;
    lastcodelenth=0;
    memset(lenth,0,sizeof(lenth));
    memset(ps,0,sizeof(ps));
}

int CHuffMan::GetSouceLen()
{                                            //获取当前工作项的源文件长度
    return soucelen;
}

int CHuffMan::GetTargetLen()
{                                            //获取当前工作项的目标文件长度
    return targetlen;
}

void CHuffMan::GetHuffCode()
{                                            //输出当前工作项的编码前缀码方案
    int i;
    for(i=0;i<ASCIIL;i++){
        if(lenth[i]>0){                        //如果前缀码不空
            printf("%c : ",i);                //输出ASCII码
            for(int j=0;j<lenth[i];j++){
                printf("%d",code[i][j]);    //输出对应前缀码
            }
            puts("");
        }
    }
}

void CHuffMan::EnCodeFile(char sfilename[],char gfilename[])
{
                                            //将文件sfilename
                                            //压缩为文件gfilename[]
    FILE *fp,*fpt;
    int c,data,l,i;
    
    EnCodePre(sfilename);                    //压缩预处理,生成哈曼树及
                                            //字符前缀码
    
    fp=fopen(sfilename,"rb");
    fpt=fopen(gfilename,"wb");
    
    SaveHuffHead(fpt);                        //写入压缩文件的头信息
                                            //!!!注意,此时lastcodelenth
                                            //为空,需压缩结束时重置
    l=data=0;
    puts("Encoding ... ");


    clock_t start = clock();
    clock_t finish;
    double consumeTime;
    int i1=0;

                                            //编码压缩过程,依次对源文件字符进行编码
    while(true){                            //存入一个字符中,用移位操作实现,每8位前
        c=fgetc(fp);                        //缀码对应一个字符,将该字符存入目标文件,
        if(feof(fp)) break;                    //最终不足8位的记录最后一位占用的前缀码长度
        soucelen++;                            //源文件长度增加
        for(i=0;i<lenth[c];i++){            //对data进行左移,空出最低位
            data<<=1;                        //对当前字符的前缀码当前们存储于data中
            data+=code[c][i];    
            if(++l%8==0){                    //满8位,则存储
                fputc(data,fpt);
                targetlen++;                //目标文件长度增加
            }
        }


    i1++;
    if(i1==soufail_charcount/4)
    {
        finish = clock();
        consumeTime = (double)(finish-start)/CLOCKS_PER_SEC;

        cout<<endl<<endl<<"已压缩:25%"<<"     "<<"用时:"<<consumeTime<<" 秒"<<endl;

    }
    if(i1==soufail_charcount/2)
    {
        finish = clock();
        consumeTime = (double)(finish-start)/CLOCKS_PER_SEC;

        cout<<endl<<endl<<"已压缩:50%"<<"     "<<"用时:"<<consumeTime<<" 秒"<<endl;

    }
    if(i1==soufail_charcount*3/4)
    {
        finish = clock();
        consumeTime = (double)(finish-start)/CLOCKS_PER_SEC;

        cout<<endl<<endl<<"已压缩:75%"<<"     "<<"用时:"<<consumeTime<<" 秒"<<endl;

    }

    }            

    finish = clock();
    consumeTime = (double)(finish-start)/CLOCKS_PER_SEC;

    cout<<endl<<endl<<"已压缩:100%"<<"     "<<"用时:"<<consumeTime<<" 秒"<<endl;
    cout<<endl<<endl;

                                            //对最后的一个字符进行处理
    lastcodelenth=l%8;                        //记录实际占用位的长度
    data<<=8-lastcodelenth;                    //空出剩余位
    fputc(data,fpt);                        //输出至文件
    targetlen++;                            //目标文件长度增加
    
    fseek(fpt,0,SEEK_SET);                    //!!!回溯至文件头,更新lastcodelenth至
    fwrite(&lastcodelenth,4,1,fpt);            //真实值
    
    fclose(fp);                                //关闭文件
    fclose(fpt);
}

void CHuffMan::DeCodeFile(char sfilename[],char gfilename[])
{                    
    
    clock_t start = clock();
    clock_t finish;
    double consumeTime;
    
                                            //解压文件sfile至gfile

    FILE *fp=fopen(sfilename,"rb");
    FILE *fpt=fopen(gfilename,"wb");
    int c,t,l,i;                            //l用于记录当前前缀码段的长度
    HuffNode cur;
    bool tmp[64];                            //tmp[]用于记录当前的前缀码段
    
    DeCodePre(fp);
    l=0;                                    
    puts("Decoding ... ");
    
    fscanf(fp,"%c",&c);                        //解码过程,压缩过程的逆过程,取出编码了的字符,
                                            //按位取出,存于tmp[]中,找出前缀码对应的字符
    while(!feof(fp)){
        soucelen++;
        fscanf(fp,"%c",&t);
        if(feof(fp))break;
        
        for(i=l+7;i>=l;i--){                //按位取出前缀码
            tmp[i]=c&1;c>>=1;
        }l+=8;
        
        while(l>=32){                        //如果当前前缀码段超出一定的长度,则取出前缀码
                                            //进行解码
            for(i=0,cur=arr[size-1];!cur.c;i++)
                cur=tmp[i]?arr[cur.rt]:arr[cur.lt];//找到前缀码段对应第一个字符
            fprintf(fpt,"%c",cur.c);                //输出至目标文件        
            l-=i;targetlen++;                        //前缀码段减去当前字符前缀码长度
            memmove(tmp,tmp+i,sizeof(bool)*l);        //数组顺移至开头,即从0开始记录当前的
                                                    //前缀码段
        }c=t;
    }
    for(i=l+7;i>=l;i--){                            //对最后一个字符做特殊处理
        tmp[i]=c&1;                                    //取出每一位
        c>>=1;
    }
    l+=lastcodelenth;                                //只利用最后一个字符的前lastcodelenth位
    while(l){                                        //输出剩余的前缀码段对应的字符
        for(i=0,cur=arr[size-1];!cur.c;i++)
            cur=tmp[i]?arr[cur.rt]:arr[cur.lt];
        fprintf(fpt,"%c",cur.c);l-=i;targetlen++;
        memmove(tmp,tmp+i,sizeof(bool)*l);
    }
    fclose(fp);fclose(fpt);                            //关闭文件

    finish = clock();
    consumeTime = (double)(finish-start)/CLOCKS_PER_SEC;

    cout<<endl<<endl<<"解压用时:"<<consumeTime<<" 秒"<<endl;
    cout<<endl<<endl;

}

bool Menu(int &op)
{
    system("cls");
    printf("|\t哈夫曼编码实现文件压缩\t|\n");
    printf("功能:\n");
    printf("_________________________________\n");
    printf("|\t1、\t压缩文件\t|\n");
    printf("|\t2、\t解压文件\t|\n");
    printf("|\t3、\t输出编码方案\t|\n");
    printf("|\t0、\t退出     \t|\n");
    printf("---------------------------------\n");
    do{
        printf("请选择:");
        scanf("%d",&op);
    }while(op<0||op>3);
    return op?true:false;
}

int main()
{
    int op;
    char file1[32],file2[32];
    CHuffMan work;
    char step[2];
    while(Menu(op)){
        switch(op){
        case 1:
            printf("请输入待压缩文件名(.txt):");
            scanf("%s",file1);
            printf("请输入压缩文件名(.huf):");
            scanf("%s",file2);

            soufail_charcount=0;
            FILE *fp1;
            fp1=fopen(file1,"rb");
            while(true){                         
                   fgetc(fp1);
                soufail_charcount++;                        
                  if(feof(fp1)) break;                
            }
            fclose(fp1);

            work.Clear();
            work.EnCodeFile(file1,file2);
            printf("源文件长度:\t%d\n",work.GetSouceLen());
            printf("目标文件长度:\t%d\n",work.GetTargetLen());
            break;
        case 2:
            printf("请输入待解压文件名(.huf):");
            scanf("%s",file1);
            printf("请输入解压后文件名(.txt):");
            scanf("%s",file2);
            work.Clear();
            work.DeCodeFile(file1,file2);
            printf("源文件长度:\t%d\n",work.GetSouceLen());
            printf("目标文件长度:\t%d\n",work.GetTargetLen());
            break;
        case 3:
            work.GetHuffCode();
            break;
        }
        puts("按任意键继续...");
        gets(step);
        gets(step);
    }
    return 0;
}
 

猜你喜欢

转载自blog.csdn.net/u013595395/article/details/85224591