• 基础数据结构-二叉树-赫夫曼树的解码


      本篇是上一篇赫夫曼树构建与编码的后续,稍微详细讲一下解码的算法。

    Huffman解码算法流程:

    1.定义指针p指向赫夫曼树结点,实际是记录结点数组的下标;

    2.定义指针i指向编码串,定义ch逐个取编码串的字符;

    3.初始化:读入编码串,设置p指向根结点,i为0;

    4.执行以下循环:

      a)取编码串的第i个字符放入ch;

      b)如果ch是字符0,表示往左孩子移动,则p跳转到右孩子;

      c)如果ch是字符1,表示往右孩子移动,则p跳转到右孩子;

      d)如果ch非0非1,表示编码串有错误,输出error表示解码失败;

      e)检查p指向的结点是否为叶子;

        i.如果是叶子,输出解码,p跳回根节点;

        ii.如果不是叶子,设置ch为3;

      f)    继续循环,一直到编码串末尾;

    5.循环执行完后,如果ch值为3,输出解码失败,否则成功结束。

    解码关键:赫夫曼编码是一种前缀编码,解码时不会因为编码串混肴;

    例如:

    字符串AACACBDEDDDD 编码后我们可以获得对应的前缀:

    A: 10;B: 1110;C: 110;D: 0;E: 1111

    它对应的编码串即为:

    下面是参考代码,注释我也打得比较详细了,算是边对着代码边讲解吧:

    #include <iostream>
    #include <string>
    #include <cstring>
    using namespace std;
    #define error -1
    #define ok 1
    
    const int MaxW = 9999;    //假设结点权值不超过9999
    
    //定义huffman树结点类
    class HuffNode
    {
        public:
        int weight;        //权值
        int parent;        //父结点下标
        int leftchild;    //左孩子下标
        int rightchild;    //右孩子下标
        char data;    //解码数据
    };
    
    //定义huffman树类
    class HuffMan 
    {
        private:
        void MakeTree();    //建树,私有函数,被公有函数调用
        void SelectMin(int pos, int *s1, int *s2);
        //从1到pos的位置找出权值最小的两个结点,结点下标存在s1和s2中
        public:
        int len;        //结点数量
        int lnum;    //叶子数量    
        HuffNode *huffTree;    //huffman树,用数组表示
        string * huffCode;        //每个字符对应的huffman编码
        void MakeTree(int n,int wt[],char data[]);    //公有函数,被主函数main调用
        void Coding();        //公有函数,被主函数main调用
        void Destroy();
        int Decode(const string codestr,char txtstr[]);
    };
    
    //构建huffman树
    void HuffMan::MakeTree(int n,int wt[],char data[])        //参数是叶子结点数量和叶子权值
    {//公有函数,对外接口
        int i;
        lnum = n;
        len = 2*n-1;
        huffTree = new HuffNode[2*n];
        huffCode = new string [lnum+1];        //位置从1开始计算
        //huffCode实质是个二维字符数组,第i行表示第i个字符对应的编码
        
        //huffman树huffTree初始化
        for(i=1;i<=len;i++)
        {
            huffTree[i].weight = wt[i-1]; //第0号不用,从1开始编号
            huffTree[i].data=data[i-1];    //解码用
        }
        for(i=1;i<=len;i++)
        {
            if(i>n) huffTree[i].weight=0;    //前n个结点是叶子,已经设值
            huffTree[i].parent = 0;
            huffTree[i].leftchild = 0;
            huffTree[i].rightchild=0;
        }
        MakeTree();    //调用私有函数建树
    }
    
    void HuffMan::SelectMin(int pos,int *s1,int *s2)
    //找出最小的两个权值的下标
    //函数采用地址传递的方法,找出的两个下标保存在s1和s2中
    {
        int w1,w2,i;
        w1=w2=MaxW;        //初始化w1和w2为最大值,在比较中会被实际的权值替换
        *s1=*s2=0;
        for(i=1;i<=pos;i++)    //对结点数组进行搜索
        {
            if(huffTree[i].weight < w1 && huffTree[i].parent == 0)
            {    //如果i结点的权值小于w1,且第i结点是未选择的结点(父亲为0)
                w2 = w1;        //把w1,s1保存到w2,s2,即原来的第一最小值变成第二最小值(不理解?)
                *s2 = *s1;
                w1 = huffTree[i].weight;    //把i结点的权值和下标保存到w1,s1,作为第一最小值
                *s1=i;
            }
            else if(huffTree[i].weight < w2 && huffTree[i].parent == 0)
            {
                w2=huffTree[i].weight;*s2=i;
            }
            //否则如果i结点的权值小于w2,且i结点是未选中的,把i结点的权值和下标保存到w2和s2,作为第二最小值    
        }
    }
    
    void HuffMan::MakeTree()
    {//私有函数,被公有函数调用
        int i,s1,s2;
        //构造huffman树HuffTree的n-1个非叶子结点
        for(i=lnum+1;i<=len;i++)
        {
            SelectMin(i-1,&s1,&s2);    //找出两个最小权值的下标放入s1和s2中
            for(i = lnum+1;i <= len;i++)
            {
                SelectMin(i-1,&s1,&s2);    //找出两个最小权值的下标放入s1,s1
                huffTree[s1].parent = i;        //将找出的两棵权值最小的字数合并为一颗子树
                huffTree[s2].parent = i;        //结点s1,s2的父亲设为i
                huffTree[i].leftchild = s1;    //i的左右孩子为s1,s2
                huffTree[i].rightchild = s2;    
                huffTree[i].weight = huffTree[s1].weight + huffTree[s2].weight;    //i的权值等于s1,s2权值和
            }
        }
    }
    
    //销毁huffman树
    void HuffMan::Destroy()
    {
        len = 0;
        lnum = 0;
        delete []huffTree;
        delete []huffCode;
    }
    
    //huffman编码
    void HuffMan::Coding()
    {    
        char *cd;
        int i,c,f,start;
        
        //求n个叶结点的huffman编码
        cd = new char[lnum];        //分配求编码的工作空间
        cd[lnum-1] = '';
        for (i=1;i<=lnum;++i)    //逐个字符求huffman编码
        {
            start = lnum-1;    //编码结束符位置
            //书P147第二个for有参考
            for(c = i,f = huffTree[i].parent; f != 0; c = f, f = huffTree[f].parent)    //从叶子到根逆向球编码
                if(huffTree[f].leftchild == c)
                {
                     cd[--start] = '0';
                }
                else
                {
                    cd[--start] = '1';
                }
                huffCode[i] = new char[lnum-start];    //为第i各字符编码分配空间
                huffCode[i].assign(&cd[start]);    //把cd中从start到末尾的编码复制到huffCode中
        }
        delete []cd;    //释放工作空间
    }
    
    //huffman解码
    int HuffMan::Decode(const string codestr,char txtstr[])  
    {
        //编码串是codestr,解码结果放在txtstr中
        //编码0表示往左孩子移动,编码1表示往右孩子移动 
        int i,k,c;
        char ch;
        c = len;    //c表示结点数组的下标,根节点是结点数组的最后一个结点,所以c一开始指向根节点
        k = 0;        //tstr的指针  
        for(i = 0;i < codestr.length();i++)  
        {
            ch = codestr[i];    //取编码串的第i个字符放入变量ch  
            if(ch == '0')        //如果ch是字符0,向左孩子移动,c跳转到左孩子
            {
                c = huffTree[c].leftchild;
            }
            else if(ch == '1')    //同理
            {
                c = huffTree[c].rightchild;
            }
            else                //解码失败
            {
                return error;
            }
            if(huffTree[c].leftchild == 0 && huffTree[c].rightchild == 0)  
            {//c指向结点是否叶子,解码串txtstr的第k保存结点c内字符,c跳回根结点
                txtstr[k] = huffTree[c].data;  
                k++;  
                c = len;  
            }  
            else
            {
                ch ='';    //用于检查不完整编码的报错
            }
        }
        if(ch == '') return error;
        else txtstr[k] = '';  //解码成功,加入字符串结束符  
        return ok;  
    }  
    
    
    //主函数
    int main()
    {
        int t,n,i,j,k;
        int wt[800];
        HuffMan myHuff;
        string codestr;
        char txtstr[800];
        char data[800];
        cout<<"请输入解码实例的个数,按回车结束:"<<endl;
        cin>> t;
        for(i=0;i<t;i++)
        {
            cout<<"请输入上一步赫夫曼编码第"<<i+1<<"个实例所得权值个数,按回车结束:"<<endl;
            cin>>n;
            cout<<"请输入上一步赫夫曼编码第"<<i+1<<"个实例所得各个权值(空格间隔),按回车结束后在下一行输入对应的字符,按回车结束:"<<endl;
            for(j=0;j<n;j++)
                cin>> wt[j];
            for(j=0;j<n;j++)
                cin>>data[j];
            myHuff.MakeTree(n,wt,data);
            myHuff.Coding();
            cout<<"请输入需要解码的编码串个数,按回车结束:"<<endl;
            cin>> k;
            for (j=1;j<=k;j++)
            {
                cout<<"请输入需要解码的第"<<j<<"个编码串,按回车结束:"<<endl;
                cin>>codestr; 
                if(myHuff.Decode(codestr,txtstr) == 1) 
                { 
                    cout<<"解码结果:"<<txtstr<<endl; 
                } 
                else cout<<"Error!您输入的编码有误,请重新运行本程序并输入正确编码。"<<endl;
            }
            myHuff.Destroy();
        }
        return 0;        
    }

    以下是我在Huffman编码解码过程中的关键点,总结如下:

    1.编码从叶子开始,解码则从根结点开始;

    2.数组下标最开始指向赫夫曼树根结点,最终结束于叶子;

    3.根据编码表,编码串0为左孩子,1为右孩子,根据左右孩子跳到相应结点,左右孩子为0则解码完成;

    4.如果编码串扫描完后停在非叶子结点(左右孩子均为0的中间结点),则解码失败需要报错;

    作者:Nathaneko

  • 相关阅读:
    ZOJ3329One Person Game(循环型 数学期望)
    ZOJ3551Bloodsucker (数学期望)
    HDU3853LOOPS (师傅逃亡系列•三)(基础概率DP)
    HDU4035 Maze(师傅逃亡系列•二)(循环型 经典的数学期望)
    阿里云DataV专业版发布,为可视化创造更多可能!
    Logtail提升采集性能
    达摩院首席数据库科学家李飞飞:云原生新战场,我们如何把握先机?
    什么是最佳的视频用户体验?阿里云视频服务四大体验优化实践
    Lambda plus: 云上大数据解决方案
    基于大数据的舆情分析系统架构
  • 原文地址:https://www.cnblogs.com/nathaneko/p/6497982.html
Copyright © 2020-2023  润新知