首页 > 代码库 > 基础数据结构-二叉树-赫夫曼树的解码(详解)

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

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

 

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 1const 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=http://www.mamicode.com/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] = \0;    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 =\0;    //用于检查不完整编码的报错        }    }    if(ch == \0) return error;    else txtstr[k] = \0;  //解码成功,加入字符串结束符      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

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