'); } '); } 哈夫曼树和哈夫曼编码 | Journey to paradise

哈夫曼树和哈夫曼编码


哈夫曼树和哈夫曼编码

引例

为了更好地理解哈夫曼树的用途我们先来看一个引例 哈夫曼树引例
哈夫曼树引例
哈夫曼树引例

什么是哈夫曼树?

首先我们来了解一下什么是带权路径长度:从树根结点到任意结点的路径长度(经过的边数)与该结点上权值的乘积,称为该结点的带权路径长度。
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。哈夫曼树并不唯一,但带权路径长度一定是相同的。利用哈夫曼树可以根据结点不同的查找频率来提高搜索效率

怎么构建哈夫曼树?

算法:
  1. 将叶子结点按权值从小到大排序构成森林
  2. 从森林中取出最小的两个结点构成一棵新树,两较小结点作为新树的左右子树,新树的权值为两较小结点权值之和,从森林中删去取出的结点并将构成的新树加入森林中( 注意:我们需要保证Huffman树的编码是前缀编码,因此它必须是一颗满树,同时只能在叶子节点中存储具体的字符。
  3. 重复操作2,直到森林中只剩一棵树,该树即为哈夫曼树

图示:
eg:有结点{10,20,30,40},以这四个权值构建哈夫曼树的过程如下
构建哈夫曼树图解

哈夫曼编码

什么是哈夫曼编码?

出现频率较高的符号用较短的编码表示,出现频率较低的符号用较长的编码表示。这样就降低了编码之后的字符串长度,达到压缩数据的目的。对于一个已经压缩好的数据文件,我们只需要拥有对应的编码表,则可将其展开。

哈夫曼树的操作

准备工作:

有关c++优先队列(priority_queue)的用法可以看 这里

    #include <iostream>
    #include <queue>
    #include <map>
    #include <sstream>
    using namespace std;
    
    /** 定义二叉树Htree (HuffmanTree)*/
    typedef
    struct Node
    {
        float weight;  // 权:叶子权为字符频次,有分支结点则为左右子树权值和
        char alpha;    // 结点字符:叶子结点为编码字符,分支结点字符则为哑元'\0'
        Node *lch;
        Node *rch;
        Node(float e,char ch=0,Node *lt=0, Node *rt=0) // ctor构造根结点:权值e,字符ch,左右子树lt,rt
            :weight(e),alpha(ch),lch(lt),rch(rt) {}
    }*HTree;
    
    /* 重载运算符()的类,其行为仿似函数,故名仿函数
    Lt定义了树之优先级:根权值大者优先级高 */
    struct Lt
    {
        bool operator()(HTree ta, HTree tb)
        {
            return ta->weight > tb->weight;
        }
    };
    
    typedef map<char,string> Dict; // 编码字典类型Dict:字符为键,编码为值
    

哈夫曼树的构建

    /*利用字符数组alpha[0..n-1]和对应权值数组ws[0:n-1],构造HuffmanTree*/
    HTree  CreateHT(char alphs[],float ws[], int n)
    {
        /*优先队列类:元素类型是树,底层采用顺序表vector,元素优先级采用Lt类中定义的operator()*/
        priority_queue< HTree, vector<HTree>, Lt > forest;
    
        for(int i=0; i<n; i++)  //输入n个结点,创建结点的树林 
        {
            forest.push( new Node(ws[i],alphs[i]));
        }
        for(int i=0; i<n-1; i++)   //两两合并n-1次
        {
            HTree t1 = forest.top();   //取出最小的树t1
            forest.pop();              //删去t1 
            HTree t2 = forest.top();   //再取最小的t2
            forest.pop();              //删去t2 
    
            float w = t1->weight + t2->weight;
            forest.push(new Node(w,0,t1,t2));   // 合并为新树,新数结点字符为'\0',加入森林
        }
    
        return forest.top(); // 森林中最后一棵树 即所建huffman树
    }

生成密码字典(哈夫曼编码)

    /*通过遍历Hufman树t,生成密码字典dict*/
    void generateDict(HTree t, Dict &dict)
    {
        static string code; //字符密码
    
        if(0==t)
            return;
        if(t->alpha!=0)     //达到叶子结点,得该字符的编码(唯叶子字符不为\0)
            dict[t->alpha] = code; // 字典增一项
    
        code +='0';  // 左走为0
        generateDict(t->lch,dict);
        code.erase(code.end()-1); // 回溯:删除进入左子树时,在串尾添加的0
    
        code+='1';  // 右走为0
        generateDict(t->rch,dict);
        code.erase(code.end()-1); // 回溯:删除进入右子树时,在串尾添加的1
    
        return;
    }
    

对字符串进行编码

    /**使用编码字典dict,返回串txt的编码*/
    string encode(string txt,  Dict dict)
    {
        string code;
    
        for(int i=0; i<txt.size(); i++)
            code += dict[ txt[i] ]; // 查字典dict得txt[i]的编码
    
        return code;
    }
    

解码

    /**使用Huffman树t解码code,返回解码后的报文*/
    string Decode(HTree t, string code)
    {
        string txt;
        istringstream in(code); // 字符串输入流 in
        Node *p = t; // 从根开始解码
    
        char dire;
    
        while( in>>dire ) // 依次读入各字符
        {
            p = ('0'==dire ? p->lch :p->rch);  // 0向左走,1向右走
    
            if( p->alpha )  //到达叶子得一字符
            {
                txt += p->alpha;
                p = t;  // 重新从根开始解码下一个字符
            }
        }
    
        return txt;
    }
    

主函数

    int main()
    {
        char alphs[] = "ABCD";
        float ws[] = {9,4,5,2};//分别对应ABCD的权值
        int n = 4;
        Dict dict; // 编码字典
    
        HTree t = CreateHT(alphs,ws, n); // 构造Huffman树t
        generateDict(t,dict);   //以Huffman树来构造字典dict
    
        cout << encode("ABC",dict) <<endl;  // 输出"ABC"的编码
        cout << Decode(t,"011110");   // 输出编码"011110"的报文
    
        return 0;
    }

运行结果:

    011110
    ABC
   

文章作者: 涂爽
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 涂爽 !
评论
 上一篇
图的基本操作 图的基本操作
Document 图的基本常识      图表现的是多对多的关系(链表表现的是一对一的关系,而树表现的是一对多的关系,树是图的特例)。图中储存数据
2020-10-24
下一篇 
二叉搜索树刷题总结 二叉搜索树刷题总结
原文:https://labuladong.gitee.io/algo/2/20/41/ 二叉搜索树BST并不复杂,但十分重要,许多数据结构(例如:AVL树、红黑树、B+树、线段树等)都是基于BST的思想设计的。BST在二叉树结构基础上有
2020-10-07
  目录