解析C++哈夫曼树编码和译码的实现

解析C++哈夫曼树编码和译码的实现

前言

哈夫曼树是一种经典的数据结构,常用于数据压缩和编解码等场景。其中,哈夫曼树的编码和译码是哈夫曼编码最核心的两个操作。

本篇文章将详细讲解如何使用C++实现哈夫曼树的编码和译码,包括以下内容:

  1. 哈夫曼树的构建
  2. 哈夫曼编码的生成
  3. 哈夫曼编码的压缩
  4. 哈夫曼编码的解压

哈夫曼树的构建

哈夫曼树的构建需要先计算出每个字符出现的频率。这里我们使用一个map数据结构来记录每个字符的出现次数。

// 统计str中每个字符出现的次数
map<char, int> CountCharsFreq(string str) {
    map<char, int> freq;
    for (char c : str) {
        freq[c]++;
    }
    return freq;
}

得到每个字符的出现频率后,我们需要把它们转化为哈夫曼树的叶子节点。这里我们使用优先队列(priority_queue)来维护叶子节点,每次取出出现频率最小的两个节点进行合并。

struct HuffmanTreeNode {
    char ch;
    int freq;
    HuffmanTreeNode *left, *right;
    HuffmanTreeNode(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}
};

struct CompareNodes {
    bool operator() (HuffmanTreeNode* lhs, HuffmanTreeNode* rhs) const {
        return lhs->freq > rhs->freq;
    }
};

HuffmanTreeNode* BuildHuffmanTree(string str) {
    // 统计字符出现次数
    map<char, int> freq = CountCharsFreq(str);

    // 构建哈夫曼树的叶子节点
    priority_queue<HuffmanTreeNode*, vector<HuffmanTreeNode*>, CompareNodes> pq;
    for (auto [c, f] : freq) {
        pq.push(new HuffmanTreeNode(c, f));
    }

    // 合并叶子节点
    while (pq.size() > 1) {
        auto left = pq.top(); pq.pop();
        auto right = pq.top(); pq.pop();
        int freq_sum = left->freq + right->freq;
        auto parent = new HuffmanTreeNode('#', freq_sum);
        parent->left = left;
        parent->right = right;
        pq.push(parent);
    }
    return pq.top();
}

最终返回的根节点就是哈夫曼树的根节点。

哈夫曼编码的生成

哈夫曼编码是将字符转化为二进制编码的过程。生成哈夫曼编码的方法是从哈夫曼树的根节点开始,每次向左走标记0,向右走标记1,直到到达叶子节点,将走过的路径上的0和1拼接起来就是该叶子节点的哈夫曼编码。

void GenerateHuffmanCodes(HuffmanTreeNode* root, string prefix, map<char, string>& codes) {
    if (root == nullptr) return;
    if (root->left == nullptr && root->right == nullptr) {
        codes[root->ch] = prefix;
    } else {
        GenerateHuffmanCodes(root->left, prefix + "0", codes);
        GenerateHuffmanCodes(root->right, prefix + "1", codes);
    }
}

以上函数中,prefix参数表示当前走过的路径,初始值为一个空字符串。codes参数则用于记录每个字符的哈夫曼编码。

哈夫曼编码的压缩

将哈夫曼编码应用到数据压缩中,其实是将字符转为对应的哈夫曼编码进行拼接,然后使用二进制数据存储。

例如,字符串abbbcdee的哈夫曼编码为0 111 110 10 01 011,二进制数据为011111101001011010,将其存储为字节数组则为0x3F 0x4A 0xD0

string CompressUsingHuffman(string str, map<char, string>& codes) {
    string compressed;
    for (char c : str) {
        compressed += codes[c];
    }
    string result;
    result.resize((compressed.size() + 7) / 8);
    for (int i = 0; i < (int)compressed.size(); i++) {
        if (compressed[i] == '1') {
            result[i / 8] |= (1 << (7 - i % 8));
        }
    }
    return result;
}

以上代码中,compressed是原始字符串转化为哈夫曼编码后的结果。之后再将其每8位转化为一个字节,并存储到字符串中。

哈夫曼编码的解压

对于使用哈夫曼编码压缩的数据,我们需要进行解压操作。解压的过程也是从哈夫曼树的根节点开始,对输入数据逐个处理,根据不同情况向左或者向右继续走下去,直到遇到叶子节点返回字符值。

string DecompressUsingHuffman(string data, HuffmanTreeNode* root) {
    string result;
    HuffmanTreeNode* ptr = root;
    for (char byte : data) {
        for (int i = 7; i >= 0; i--) {
            int bit = (byte >> i) & 1;
            ptr = (bit == 0 ? ptr->left : ptr->right);
            if (ptr->left == nullptr && ptr->right == nullptr) {
                result += ptr->ch;
                ptr = root;
            }
        }
    }
    return result;
}

其中,data是压缩后的二进制数据,root是哈夫曼树的根节点。

示例说明

示例1

输入字符串:

abbbcdee

输出压缩后的二进制数据:

00111111 01001010 11010000

具体过程如下:

  1. 统计字符出现次数,得到如下频率表:

a: 1
b: 3
c: 1
d: 1
e: 2

  1. 构建哈夫曼树,得到如下结构:

[8]
/ \
[3] [#]
/ \
[1] [2]
/ \ / \
[c][a][d][e]

  1. 生成哈夫曼编码,得到如下表格:

a: 10
b: 00
c: 110
d: 111
e: 01

  1. 将原始字符串转化为哈夫曼编码,并每8位转化为一个字节:

abbbcdee → 001111101001011010

示例2

输入字符串:

hello world!

输出压缩后的二进制数据:

10011000 10000000 11010101 10110111 10011000 00111110 00011001

具体过程与示例1相似,这里不再赘述。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:解析C++哈夫曼树编码和译码的实现 - Python技术站

(0)
上一篇 2023年5月24日
下一篇 2023年5月24日

相关文章

  • 基于Java中Math类的常用函数总结

    基于Java中Math类的常用函数总结 简介 Java的Math类为开发者提供了许多数学方法,使用这些方法能够方便地对数据进行处理和计算。本篇文章将对Java中Math类的一些常用函数进行总结和详细讲解,包括:绝对值函数、对数函数、三角函数等。 绝对值函数 绝对值函数在数学中也称为模函数,是一个常用的函数。在Java中,可以使用Math类中的abs函数来计算…

    C 2023年5月22日
    00
  • C语言实现制作通讯录(新手推荐)

    介绍 制作一个简单的通讯录是C语言初学者学习的一个非常有趣的项目。本教程将为大家提供一个完整的实现过程,旨在帮助初学者全面掌握C语言编程的基本技能。 步骤 创建一个新的C语言文件。 打开你的编辑器,并创建一个新的C语言文件。保存文件,并为该文件选择一个描述性名称,例如“AddressBook.c”。 引入所需的头文件。 通常情况下,我们需要使用stdio.h…

    C 2023年5月23日
    00
  • 黑暗之魂3一级无伤BOSS打法技巧分享

    黑暗之魂3一级无伤BOSS打法技巧分享 本攻略主要分享黑暗之魂3游戏中一级无伤BOSS打法技巧。 前置条件 游戏难度为一级; 要求无伤过关。 BOSS打法 在游戏的各大BOSS中,以下四个BOSS比较难打,需要注意一些技巧。 1. 赫瑞默尔 赫瑞默尔是一只霸气的老鼠,非常善于变幻,这个BOSS的攻击方式非常的火爆。 为了躲避赫瑞默尔的攻击,需要做到以下几点:…

    C 2023年5月22日
    00
  • 好玩又实用的查看函数图像网站Desmos

    漂亮好用的函数图像绘制工具Desmos,可以让用户轻松实现多种不同的任务,包括绘制平面图形、计算数值、函数绘图和数据可视化等。本文将以完整的攻略形式,为你详细讲解如何使用Desmos网站绘制、调整并分享函数图像。 一、注册Desmos账户 首先打开官方网站https://www.desmos.com,点击右上角的“Sign In”按钮,选择“Sign up”…

    C 2023年5月22日
    00
  • Python中with上下文管理协议的作用及用法

    下面就来详细讲解“Python中with上下文管理协议的作用及用法”的完整攻略。 什么是上下文管理协议 在Python中,上下文管理指的是在资源使用中的安全获取和释放的机制。这个机制就是基于Python的上下文管理协议实现的。 上下文管理协议是指有赖于特定的方法支持协议的对象的协议。这些方法包括__enter__和__exit__,称为上下文管理器。使用这种…

    C 2023年5月23日
    00
  • 基于C++编写一个键盘提示音程序

    关于基于C++编写一个键盘提示音程序的攻略,我将为您提供以下完整的指导: 步骤一:了解键盘输入的基础知识 在编写键盘提示音的程序之前,我们需要了解一些基础概念: 键盘布局:键盘上每一个按键的位置; 扫描码:键盘上每一个按键都有一个与之对应的扫描码,用于唯一地识别每一个按键; ASCII码:每一个扫描码都对应了一个ASCII码,用于标示按键所对应的字符。 步骤…

    C 2023年5月23日
    00
  • Java8 ArrayList之forEach的使用

    下面我将为你详细讲解“Java8 ArrayList之forEach的使用”的完整攻略。 1. Java8 ArrayList的使用 在Java中,ArrayList是一种常见的集合类型,它继承自List接口,可以存储多个元素,并且支持动态数组的特性,可以自动扩容。下面是ArrayList的定义: public class ArrayList<E&gt…

    C 2023年5月23日
    00
  • c语言实现奇偶排序算法

    下面是详细讲解“c语言实现奇偶排序算法”的完整攻略: 什么是奇偶排序算法 奇偶排序算法,也称为奇偶交换排序算法,是一种简单的排序算法。它的特点是同时进行奇数与偶数位置的元素比较和交换,直到序列有序为止。 奇偶排序算法的实现 奇偶排序算法的实现过程可以分为两个阶段,一阶段是进行奇偶位置上元素的比较和交换,二阶段是将相邻的元素比较和交换,两个阶段交替执行,直到序…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部