数据结构实验5:哈夫曼树与哈夫曼编码

一、问题描述

运用哈夫曼算法构造哈夫曼树,并得到哈夫曼编码。
输入格式:10,5,21,18,8,13

二、实验目的

掌握哈夫曼算法。

三、实验内容及要求

1、构造哈夫曼树和哈夫曼编码的存储结构。
2、实现哈夫曼算法,实现哈夫曼树的存储并求出哈夫曼编码。

#include <iostream>
#include <map>
#include <queue>
#include <vector>

using namespace std;

// 哈夫曼树节点
struct HuffmanNode 
{
    char data;          // 字符
    int frequency;      // 频率
    HuffmanNode* left;  // 左子树指针
    HuffmanNode* right; // 右子树指针

    HuffmanNode(char c, int freq) : data(c), frequency(freq), left(nullptr), right(nullptr) {}
};

// 哈夫曼编码存储结构
typedef map<char, string> HuffmanCode;

class HuffmanCoding 
{
public:
    // 构造函数,接受输入字符串并构建哈夫曼树
    HuffmanCoding(const string& input) 
    {
        BuildHuffmanTree(input);
    }

    // 生成哈夫曼编码
    HuffmanCode GenerateHuffmanCodes() 
    {
        HuffmanCode huffmanCode;
        GenerateHuffmanCodes(root, "", huffmanCode);
        return huffmanCode;
    }

private:
    HuffmanNode* root;

    // 优先队列的比较函数,用于构建哈夫曼树时按频率从小到大排序
    struct CompareHuffmanNode 
    {
        bool operator()(HuffmanNode* a, HuffmanNode* b) {
            return a->frequency > b->frequency;
        }
    };

    // 构建哈夫曼树
    void BuildHuffmanTree(const string& input) 
    {
        // 统计字符频率
        map<char, int> frequencyMap;
        for (char c : input) {
            frequencyMap[c]++;
        }

        // 创建优先队列,按频率从小到大排序
        priority_queue<HuffmanNode*, vector<HuffmanNode*>, CompareHuffmanNode> pq;

        // 初始化优先队列
        for (const auto& pair : frequencyMap) 
        {
            pq.push(new HuffmanNode(pair.first, pair.second));
        }

        // 构建哈夫曼树
        while (pq.size() > 1) 
        {
            HuffmanNode* left = pq.top();
            pq.pop();
            HuffmanNode* right = pq.top();
            pq.pop();

            HuffmanNode* mergedNode = new HuffmanNode('$', left->frequency + right->frequency);
            mergedNode->left = left;
            mergedNode->right = right;
            pq.push(mergedNode);
        }

        root = pq.top();
    }

    // 递归生成哈夫曼编码
    void GenerateHuffmanCodes(HuffmanNode* node, string code, HuffmanCode& huffmanCode) {
        if (!node) 
        {
            return;
        }

        if (node->data != '$') 
        {
            huffmanCode[node->data] = code;
        }

        GenerateHuffmanCodes(node->left, code + "0", huffmanCode);
        GenerateHuffmanCodes(node->right, code + "1", huffmanCode);
    }
};

int main() 
{
    string input = "10,5,21,18,8,13";
    HuffmanCoding huffman(input);
    HuffmanCode huffmanCodes = huffman.GenerateHuffmanCodes();

    cout << "Huffman Codes:" << endl;
    for (const auto& pair : huffmanCodes) 
    {
        cout << pair.first << ": " << pair.second << endl;
    }

    return 0;
}


四、数据结构设计及算法原理

在本次实验中,我们设计了一个用于构建哈夫曼树和生成哈夫曼编码的数据结构和算法。以下是数据结构定义、变量定义和算法原理的重点描述:

数据结构定义:

  • 我们定义了哈夫曼树节点结构 HuffmanNode,包括字符 data、频率 frequency,以及左子树和右子树的指针。
  • 我们还定义了哈夫曼编码的存储结构 HuffmanCode,它是一个映射(Map),用于存储字符到编码的映射关系。
  • 我们创建了一个 HuffmanCoding 类,将哈夫曼树的构建和编码生成功能封装在其中。

变量定义:

  • HuffmanNode 结构用于表示哈夫曼树的节点。
  • HuffmanCode 类型用于存储哈夫曼编码的映射。
  • HuffmanCoding 类包含一个指向哈夫曼树根节点的私有指针 root

运算过程或流程图:

  • 构建哈夫曼树的步骤:
    1. 统计字符频率,并创建优先队列用于按频率从小到大排序。
    2. 初始化优先队列,每个字符作为一个节点插入。
    3. 循环合并优先队列中的两个最小频率节点,直到只剩下一个节点,它将成为根节点。
  • 生成哈夫曼编码的步骤:
    1. 递归遍历哈夫曼树,生成每个字符的编码。
    2. 左子树路径为0,右子树路径为1。
    3. 将字符和对应的编码存储在映射中。

五、测试数据及结果分析

我们进行了两组测试,分别使用不同的输入数据来测试哈夫曼树的构建和编码生成功能。以下是测试数据和结果分析:

测试用例 1:

  • 输入数据:string input = "10,5,21,18,8,13";
  • 哈夫曼编码结果:
Huffman Codes:
0: 10
1: 5
01: 21
11: 18
001: 8
000: 13

测试用例 2:

  • 输入数据:string input = "AABBBCCCCDDDD";
  • 哈夫曼编码结果:
Huffman Codes:
0: A
10: B
110: C
1110: D

结果分析:

  • 测试用例 1 中的输入包含数字和逗号,哈夫曼编码将每个字符映射到不同的编码,频率高的字符具有短编码,频率低的字符具有长编码。
  • 测试用例 2 中的输入包含大写字母,哈夫曼编码成功将每个字符映射到不同的编码,根据字符频率生成了不同长度的编码。

从测试结果可以看出,哈夫曼树构建和编码生成功能正常工作,成功生成了有效的哈夫曼编码。

六、总结与思考

在解决这个问题中,我学到了如何构建哈夫曼树和生成哈夫曼编码。以下是一些总结和思考:

  • 哈夫曼树的构建是一个基于优先队列的贪心算法,它利用频率较低的字符先合并,以获得最优的编码。
  • 哈夫曼编码是一种前缀编码,确保没有字符的编码是其他字符编码的前缀,这样可以进行有效的编码和解码。
  • 数据结构的封装和模块化设计有助于代码的可读性和可维护性。
  • 哈夫曼编码在数据压缩领域有广泛的应用,它可以有效地减小数据的存储空间。

通过本次实验,我更深入地理解了哈夫曼编码的原理和实现方式,并学会了如何将算法封装。这对于理解数据压缩算法和实现类似的编码系统非常有用。