LogoCSP Wiki By Yundou
图论

霍夫曼树与编码

简介与概述、功能介绍

霍夫曼树

霍夫曼树(Huffman Tree),又称最优二叉树,是一种带权路径长度(WPL)最短的二叉树。树的带权路径长度是指树中所有叶子节点的权值乘以其到根节点的路径长度之和。构建霍夫曼树的目的就是使这个带权路径长度达到最小。

wiw_i 为二叉树第 ii 个叶结点的权值,lil_i 为从根结点到第 ii 个叶结点的路径长度,则 WPL 计算公式如下:

WPL=i=1nwiliWPL=\sum_{i=1}^nw_il_i

霍夫曼编码

霍夫曼编码(Huffman Coding)是一种变长编码方式,它利用霍夫曼树对数据进行编码。在数据通信中,经常需要将字符转换为二进制编码进行传输。霍夫曼编码根据字符出现的频率来分配编码长度,出现频率高的字符使用较短的编码,出现频率低的字符使用较长的编码,从而达到压缩数据的目的。

适用场景

霍夫曼树

  • 数据压缩:霍夫曼树是霍夫曼编码的基础,通过构建霍夫曼树可以实现高效的数据压缩。
  • 编码优化:在一些需要对数据进行编码的场景中,使用霍夫曼树可以得到最优的编码方案。

霍夫曼编码

  • 文件压缩:在文件存储和传输中,使用霍夫曼编码可以显著减少文件的大小,提高存储和传输效率。
  • 数据通信:在数据传输过程中,使用霍夫曼编码可以减少数据的传输量,降低通信成本。

算法原理详解

霍夫曼树的构建

  1. 初始化:将每个字符作为一个单独的节点,节点的权值为该字符的出现频率。
  2. 排序:将所有节点按照权值从小到大排序。
  3. 合并:取出权值最小的两个节点,合并成一个新节点,新节点的权值为这两个节点的权值之和。
  4. 插入:将新节点插入到节点集合中,并重新排序。
  5. 重复步骤 3 和 4,直到节点集合中只剩下一个节点,这个节点就是霍夫曼树的根节点。

霍夫曼编码的生成

  1. 从根节点开始,向左走标记为 0,向右走标记为 1。
  2. 遍历到叶子节点时,记录下从根节点到该叶子节点的路径上的标记,即为该字符的霍夫曼编码。

构造过程

图片描述

经典例题及代码实现

题面描述

给定一个字符串,统计每个字符的出现频率,然后构建霍夫曼树并生成霍夫曼编码,最后输出每个字符的霍夫曼编码。

输入输出样例

  • 输入
abcabc
  • 输出
a: 0
b: 10
c: 11

代码实现

#include <iostream>
#include <cstring>
using namespace std;
 
const int MAXN = 1005;
 
// 霍夫曼树节点结构体
struct Node {
    int w;  // 权值
    int l, r;  // 左右子节点编号
} nodes[MAXN];
 
// 字符频率数组
int freq[256];
// 字符的霍夫曼编码
char code[256][MAXN];
// 临时编码
char tmp[MAXN];
 
// 查找权值最小的两个节点
void findMin(int& x, int& y, int n) {
    int min1 = 0x3f3f3f3f, min2 = 0x3f3f3f3f;
    for (int i = 0; i < n; i++) {
        if (nodes[i].w < min1) {
            min2 = min1;
            y = x;
            min1 = nodes[i].w;
            x = i;
        } else if (nodes[i].w < min2) {
            min2 = nodes[i].w;
            y = i;
        }
    }
}
 
// 构建霍夫曼树
int buildHuffmanTree(int n) {
    int idx = n;
    while (n > 1) {
        int x, y;
        findMin(x, y, n);
        nodes[idx].w = nodes[x].w + nodes[y].w;
        nodes[idx].l = x;
        nodes[idx].r = y;
        nodes[x].w = 0x3f3f3f3f;
        nodes[y].w = 0x3f3f3f3f;
        n--;
        idx++;
    }
    return idx - 1;
}
 
// 生成霍夫曼编码
void generateCodes(int u, int len) {
    if (nodes[u].l == -1 && nodes[u].r == -1) {
        for (int i = 0; i < len; i++) {
            code[u][i] = tmp[i];
        }
        code[u][len] = '\0';
        return;
    }
    tmp[len] = '0';
    generateCodes(nodes[u].l, len + 1);
    tmp[len] = '1';
    generateCodes(nodes[u].r, len + 1);
}
 
int main() {
    char s[MAXN];
    cin >> s;  // 读入字符串
    int len = strlen(s);
    // 统计字符频率
    for (int i = 0; i < len; i++) {
        freq[s[i]]++;
    }
    int n = 0;
    // 初始化节点
    for (int i = 0; i < 256; i++) {
        if (freq[i] > 0) {
            nodes[n].w = freq[i];
            nodes[n].l = nodes[n].r = -1;
            n++;
        }
    }
    // 构建霍夫曼树
    int root = buildHuffmanTree(n);
    // 生成霍夫曼编码
    generateCodes(root, 0);
    // 输出每个字符的霍夫曼编码
    for (int i = 0; i < 256; i++) {
        if (freq[i] > 0) {
            cout << (char)i << ": " << code[i] << endl;
        }
    }
    return 0;
}

总结

霍夫曼树和霍夫曼编码在数据压缩和编码优化方面有着广泛的应用。通过构建霍夫曼树和生成霍夫曼编码,可以根据字符的出现频率对数据进行高效的压缩,减少数据的存储空间和传输量。在实现过程中,需要注意节点的合并和编码的生成,同时要合理处理边界情况。

On this page