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

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

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] = ‘\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

时间: 2024-10-10 21:59:35

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

【数据结构】赫夫曼树的实现和模拟压缩(C++)

赫夫曼(Huffman)树,由发明它的人物命名,又称最优树,是一类带权路径最短的二叉树,主要用于数据压缩传输. 赫夫曼树的构造过程相对比较简单,要理解赫夫曼数,要先了解赫夫曼编码. 对一组出现频率不同的字符进行01编码,如果设计等长的编码方法,不会出现混淆的方法,根据规定长度的编码进行翻译,有且只有一个字符与之对应.比如设计两位编码的方法,A,B,C,D字符可以用00-11来表示,接收方只要依次取两位编码进行翻译就可以得出原数据,但如果原数据只由n个A组成的,那发出的编码就是2n个0组成,这样的

数据结构 - 赫夫曼树及其应用

赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径. ② 路径长度:结点路径上的分支数目称为路径长度. ③ 树的路径长度:从树根到每一个结点的路径长度之和. ④ 结点的带权路径长度:从树的根结点到该结点的的路径长度与结点的权(值)的乘积. 权(值):各种开销.代价.频度等的抽象称呼. ⑤ 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记做: WPL

赫夫曼树编码解码实例(C)

//HuffmanTree.h #include <stdlib.h> #include <stdio.h> #include <string.h> #define OVERFLOW -1 typedef struct{ char data; //节点所存字符 unsigned int weight; //节点权重 unsigned int parent,lchild,rchild; }HTNode, *HuffmanTree; //动态分配数组存储赫夫曼树 typed

javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径. ② 路径长度:结点路径上的分支数目称为路径长度. ③ 树的路径长度:从树根到每一个结点的路径长度之和. 以下图为例: A到F :结点路径 AEF : 路径长度(即边的数目) 2 : 树的路径长度:3*1+5*2+2*3=19: ④ 结点的带权路径长度:从该结点的到树的根结

数据结构之二叉树,赫夫曼树(C++版)

#include <iostream>#include <windows.h>#include <stdlib.h>#include <string.h>#define MAXLISTSIZE 100 //预设的存储空间最大容量#define FALSE 0#define TRUE 1using namespace std;typedef char ElemType; typedef struct BiTNode{ ElemType data; struct

Huffman tree(赫夫曼树、霍夫曼树、哈夫曼树、最优二叉树)

flyfish 2015-8-1 Huffman tree因为翻译不同所以有其他的名字 赫夫曼树.霍夫曼树.哈夫曼树 定义引用自严蔚敏<数据结构> 路径 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径. 路径长度 路径上的分支数目称作路径长度. 树的路径长度 树的路径长度就是从根节点到每一结点的路径长度之和. 结点的带权路径长度 结点的带权路径长度就是从该结点到根节点之间的路径长度与结点上权的乘积. 树的带权路径长度 树的带权路径长度就是树中所有叶子结点的带权路径长度之和,通常记做

由二叉树构造赫夫曼树

赫夫曼树: 假设有n个权值{w1,w2,w3....},试构造一棵具有n个叶子节点的二叉树,每个叶子节点带权为wi,则其中带权路径长度最小的二叉树称为最优二叉树或者叫赫夫曼树. 构造赫夫曼树: 假设有n个权值,则构造出的赫夫曼树有n个叶子节点,n个权值分别设置为w1,w2,....wn,则赫夫曼树的构造规则为: 1.将w1,w2...看成是有n棵树的森林: 2.在森林中选择两个根节点的权值最小的树合并,作为一棵新树的左右子树,且新树的根节点权值为其左右子树根节点权值之和: 3.从森林中删除选取的

php 二叉树 与赫夫曼树

在学习图之前,中间休息了两天,感觉二叉树需要消化一下.所以中间去温习了下sql,推荐一本工具书<程序员的SQL金典>看名字不像一本好书,但是作为一个不错的SQL工具书还是可以小小备忘一下.涵盖内容不详细但是挺广,覆盖多种主流数据库 言归正传,以前知道折半查找,二叉树的概念也是感觉挺有意思,二叉树的实现有一个案例很不错,代码如下 class BiNode{ public $data; public $lchild; public $rchild; public function __constr

赫夫曼树编码的表示与实现--自己写数据结构

头文件huffman.h #ifndef _HUFFMAN_H_ #define _HUFFMAN_H_ #define MAX_WEIGHT 10000 typedef struct _HTNode { int weight; int parent,lchild,rchild; char data; }HTNode,*pHTNode; typedef char** huffmancode; void select_min_weight(HTNode* btree,int mn,int* s1,