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

相关文章

  • angular指令笔记ng-options的使用方法

    下面我将详细讲解“angular指令笔记ng-options的使用方法”的完整攻略。首先,让我们来看一下ng-options的作用是什么。 什么是ng-options ng-options是AngularJS中的一条指令,它用于创建选项列表。在使用这个指令时,我们可以简单地通过设置相关的属性来定义可选项。ng-options指令通常与ng-model指令一起…

    C 2023年5月22日
    00
  • Recommended C Style and Coding Standards中文翻译版

    首先,需要明确“Recommended C Style and Coding Standards”是一份由美国国防部发布的规范文档,旨在规范C语言程序的编写。该文档包含了C语言编程所需的规范、风格、注释、命名、代码布局和格式等方面的建议。如何应用该文档,建立自己的编程风格呢? 以下是应用“Recommended C Style and Coding Stan…

    C 2023年5月22日
    00
  • 餐馆点菜系统C语言源代码

    餐馆点菜系统C语言源代码是一个典型的C语言项目,介绍其完整攻略包含以下内容: 一、项目介绍 介绍该项目的主要功能和特色,例如: 该项目是一个基于C语言的餐馆点菜系统,可以实现餐馆的订单管理、厨房制作菜品等功能,具备良好的用户界面和易用性,支持自定义菜品等特色功能。 二、项目需求 明确该项目的需求以及技术实现方案,例如: 该项目的需求包括餐馆订单管理、菜品库存…

    C 2023年5月23日
    00
  • AE怎么制作削碎一块的圆形动画? ae做圆形破碎部分动画的技巧

    制作圆形破碎部分动画是一种常见的AE动画效果。下面是制作该效果的完整攻略: 步骤1:准备工作 在AE中打开一个新项目,将需要制作圆形破碎部分动画的素材导入到项目中。素材可能是一张图片或一个动画序列,取决于你的需求。确保素材已经被正确地导入到项目中。 步骤2:制作Mask 创建一个新的黑色图层,用于制作遮罩(Mask)。在图层上创建一个白色的圆形遮罩(Mask…

    C 2023年5月22日
    00
  • 浅谈c++的编译和运行

    下面我会详细讲解“浅谈c++的编译和运行”的完整攻略。 一、C++编译和运行的基本流程 C++程序的编译和运行可以通过以下几个步骤来完成: 用编辑器编写C++源代码文件; 用编译器将C++源代码文件编译成可执行文件; 运行可执行文件,查看程序运行结果。 说明:可执行文件是经过编译器编译之后的最终产物,可以直接在操作系统上运行, 并生成程序输出结果。 二、C+…

    C 2023年5月23日
    00
  • 学习C语言的第一天

    今天学习C语言学习了三个部分: 第一个部分是软件环境的搭建,如何搭建一个项目 使用工具:visual studio 2010 搭建过程:新建项目、配置设置(主要是解决运行后一闪而过的问题) 第二部分是编写一个简单的C语言程序代码 #include<stdio.h> //引入头文件 io指的是输入与输出 int main(){ //不可少的入口函数…

    C语言 2023年4月18日
    00
  • c++11中关于std::thread的join的详解

    简介 在C++11中,我们可以通过std::thread类来创建一个线程。该类提供了与操作系统级别的线程相关的方法,例如创建、销毁、挂起、恢复等。线程的执行中,有可能会出现多个线程共享同一个资源导致的竞争情况,此时,我们就需要对线程进行同步,在正确的时间点上对多个线程进行操作控制。join函数就是一个非常常用的同步方法。 使用方法 join函数用于等待线程的…

    C 2023年5月22日
    00
  • C++获取多浏览器上网历史记录示例代码(支持获取IE/Chrome/FireFox)

    C++获取多浏览器上网历史记录示例代码攻略 在使用C++编程时,获取多浏览器上网历史记录是一项比较常用的操作,尤其是在开发一些浏览器小工具和浏览器扩展程序时。在这篇攻略中,我们将演示如何使用C++获取IE、Chrome和Firefox浏览器上网历史记录的示例代码,并且包含两个完整的示例说明。 支持的浏览器和实现方式 在编写代码之前,我们需要了解一下需要支持哪…

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