zoukankan      html  css  js  c++  java
  • 基于huffman编码的文件压缩与解压程序的实现

    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;
    }
  • 相关阅读:
    Navicat Premium 最新激活码 12 免安装(不用注册码,不用注册机)
    Vs Code 2019软件安装教程及常用的入门设置
    (WinForm)FormBorderStyle属性
    背包的题后总结。
    123
    网络最大流——最高标号预流推进
    厉害的网站
    一般图最大匹配——带花树
    四边形不等式
    你好,OI
  • 原文地址:https://www.cnblogs.com/liwenzhu/p/3504285.html
Copyright © 2011-2022 走看看