解析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日

相关文章

  • golang使用json格式实现增删查改的实现示例

    下面我将详细讲解一下使用 Golang 中的 json 包实现增删查改的实现示例。 增删查改简介 增删查改是非常基本的 CRUD 操作,即创建(Create)、读取(Retrieve)、更新(Update)和删除(Delete)。在 web 应用开发中,这些操作是必不可少的,而 json 格式是 web 应用开发中经常用到的数据格式。 在 Golang 中,…

    C 2023年5月23日
    00
  • C语言中实现KMP算法的实例讲解

    C语言中实现KMP算法的实例讲解 什么是KMP算法 KMP算法(Knuth-Morris-Pratt algorithm)是一种字符串匹配算法,可以在$O(n)$的时间复杂度内实现字符串的查找。KMP算法主要解决的问题是在主串S中查找模式串T的位置,KMP算法的核心思想是通过预处理模式串,构造一个跳转表格,从而在匹配的过程中能够避免主串S的回溯,从而提高算法…

    C 2023年5月22日
    00
  • C++11中bind绑定器和function函数对象介绍

    C++11中bind绑定器和function函数对象介绍 C++11引入了许多新特性,其中包括bind绑定器和function函数对象。这些特性使得C++在编写现代化的代码方面变得更加简单和灵活,为程序员提供了更多的工具来实现代码复用和组合。 bind绑定器 bind绑定器是一个函数模板,它可以用来将一个函数的参数绑定到特定的值或另一个函数。这使得我们可以轻…

    C 2023年5月22日
    00
  • C++实现图书管理系统简易版

    C++实现图书管理系统简易版攻略 前言 图书管理系统是一种基础的管理系统,它可以帮助管理员管理图书信息和读者信息,完成借阅、归还等基本操作。本文将详细介绍如何使用C++编程实现图书管理系统的简易版。 实现步骤 1. 确定需求 在编写代码之前,需要明确所要实现的功能需求。基本需求如下: 管理员可以添加图书和删除图书 管理员可以添加读者和删除读者 读者可以查询图…

    C 2023年5月24日
    00
  • win10怎么快速清理C盘 彻底清除C盘垃圾文件的几种方法

    下面我就来详细讲解一下如何快速清理win10系统的C盘,彻底清除C盘的垃圾文件。 方法一:使用系统自带的磁盘清理工具 Windows10自带了磁盘清理工具,可以用来清除系统中一些没有用的临时文件和垃圾文件等。具体操作步骤如下: 右键单击C盘,选择“属性”。 在“常规”选项卡下,单击“磁盘清理”。 选择要清除的文件类型,如“临时文件”、“下载文件”、“回收站”…

    C 2023年5月22日
    00
  • Golang Gin解析JSON请求数据避免出现EOF错误

    以下是 Golang Gin 解析 JSON 请求数据避免出现 EOF 错误的完整攻略。 1. 问题描述 当我们使用 Golang Gin 框架对请求数据进行解析时,经常会出现 EOF 错误。出现这个错误的原因是请求中的 body 数据仅能被读取一次,所以在多次请求中进行数据解析时,会出现 EOF 错误。 2. 解决方法 为了解决这个问题,我们需要将请求中的…

    C 2023年5月23日
    00
  • Windows10配置VSCode C++环境(超详细,面向小白以及大佬们)

    Windows10配置VSCode C++环境(超详细,面向小白以及大佬们) 1. 安装Visual Studio Code 首先需要安装Visual Studio Code(VSCode),可以到官网 https://code.visualstudio.com/ 下载安装包进行安装。安装完成后打开VSCode,点击左侧扩展图标,搜索”Code Runner…

    C 2023年5月23日
    00
  • C/C++ 中怎样使用SetConsoleTextAttribute()函数来控制输出字符的颜色

    当在控制台程序中使用C/C++语言输出字符时,通过SetConsoleTextAttribute()函数可以改变输出字符的颜色。该函数在Windows头文件中定义。下面给出SetConsoleTextAttribute()函数的用法及示例代码。 语法 BOOL SetConsoleTextAttribute( HANDLE hConsoleOutput, W…

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