C++优先队列实现哈夫曼编码

0x00 介绍

哈夫曼算法

  • 《数据结构与算法分析》给出定义:

    算法对一个由树组成的森林进行。一棵树的权等于它的树叶的频率的和。任意选取最小权的两棵树T1和T2,并任意形成以T1和T2为子树的新树,将这样的过程进行C - 1次。在算法的开始,存在C棵单节点树——每个字符一棵。在算法结束时得到一棵树,这棵树就是最优哈夫曼编码树。

    哈夫曼编码过程

  • 给出n个字符,对这n个字符进行编码。在这里,我们令n等于5,代表的权值分别为0.10.20.30.40.5。根据哈夫曼算法,对其进行哈夫曼编码将按照下列方式依次完成:
  • 5个结点,令其名称即为各自权值:
  • 第一步,合并0.10.2
  • 第二步,合并0.30.3
  • 第三步,合并0.40.5
  • 合并0.60.9,得到哈夫曼树:
  • 最后,按照左子树为0,右子树为1的编码规则(也可以是左子树为1,右子树为0)对树进行编码:

    从而,0.10.20.30.40.5的编码分别为010011001011

    哈夫曼编码的应用

  • 文件压缩;
  • HTTP数据包压缩;
  • 信源编码等。

0x01 问题分析

  • 对上述哈夫曼编码过程,我们可以用伪代码描述如下:

    1
    2
    3
    4
    将n个结点放入集合S
    while (S中的结点数 > 1):
    取走S中2个权值最小的结点,计算它们值之和,并加入S
    return 集合S剩下的结点 # 剩下的结点就是根结点
  • 为了高效求出集合S中2个权值最小的结点,需要借助二叉堆的数据结构(即优先队列),避免实现堆的细节,可以采用C++ STL中的priority_queue

0x02 代码实现

  • 要求
    现有input.txt文件,存放内容为0.10.20.30.40.5,将各自的哈夫曼编码输出到output.txt

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    #include <iostream>
    #include <queue>
    #include <map>
    #include <fstream>

    typedef double dataType;

    typedef struct Node {
    struct Node* lchild;
    struct Node* rchild;
    dataType data;
    } Huffman;

    struct cmp { /* 规定优先队列的排序规则 */
    bool operator() (Huffman* a, Huffman* b) const
    {
    return (a->data > b->data); /* 小顶堆 */
    }
    };

    /*
    code 变量的初始值是空字符串,采用二叉树后续遍历,如果是非叶子结点,
    存在左子树就将code加"0",存在右子树就将code加"1",继续递归调用;
    如果是叶子结点,就记录到键值对(map)里。
    */
    void huffman_code(Huffman* h, std::string code, std::map<double, std::string>& map_huff)
    {
    if (!h)
    return;
    if (h->lchild)
    huffman_code(h->lchild, code + "0", map_huff);
    if (h->rchild)
    huffman_code(h->rchild, code + "1", map_huff);
    if (h->lchild == 0 && h->rchild == 0) {
    map_huff[h->data] = code;
    return;
    }
    }

    void mem_clear(Huffman* p) /* 释放内存 */
    {
    if (!p)
    return;
    mem_clear(p->lchild);
    mem_clear(p->rchild);
    delete p;
    }

    int main()
    {
    std::priority_queue<Huffman*, std::vector<Huffman*>, cmp > Q;
    std::map<double, std::string> map_huff;
    std::ifstream fin("input.txt");
    std::string code("");
    std::ofstream fout("output.txt");

    double x;
    while (fin >> x) {
    Huffman* h = new Huffman();
    h->data = x;
    h->lchild = h->rchild = 0;
    Q.push(h);
    map_huff.insert({x, ""});
    }

    Huffman* p;
    while (Q.size() > 1) { /* 如果Q剩余结点大于1,则执行循环体 */
    p = new Huffman();
    Huffman *p1, *p2;
    p1 = Q.top(); /* 依次取出权值最小的两个结点,放入p1和p2 */
    Q.pop();
    p2 = Q.top();
    Q.pop();

    /*
    计算两结点的权值之和,存入p,且因为
    p1->data <= p2->data,可以将权值小
    的结点存入p的左子树,权值大的结点存入p
    的右子树,最后将p放入Q中
    */
    p->data = p1->data + p2->data;
    p->lchild = p1;
    p->rchild = p2;
    Q.push(p);
    }

    huffman_code(p, code, map_huff); /* p就是哈夫曼树的根结点 */
    mem_clear(p);

    for (auto& i : map_huff) {
    fout << i.first << "\t" << i.second << std::endl;
    }

    fin.close();
    fout.close();
    return 0;
    }
  • 编译环境
    g++ -std=c++11

  • 运行结果