哈夫曼编码 C++ 实现:优先队列构建 6 字符编码树(附完整代码)
2026/7/9 19:18:08 网站建设 项目流程

哈夫曼编码 C++ 实现:优先队列构建 6 字符编码树(附完整代码)

在数据压缩领域,哈夫曼编码是一种经典的无损压缩算法。它通过统计字符出现频率,构建最优二叉树来实现高效编码。本文将深入探讨如何使用 C++ 标准模板库(STL)中的优先队列(priority_queue)来实现哈夫曼编码,并提供一个完整的、可直接编译运行的代码示例。

1. 哈夫曼编码基础原理

哈夫曼编码的核心思想是变长前缀编码,即出现频率高的字符使用较短的编码,频率低的字符使用较长的编码。这种编码方式需要满足两个关键特性:

  1. 前缀属性:任何字符的编码都不是另一个字符编码的前缀
  2. 最优压缩:整体编码长度最短,压缩效率最高

构建哈夫曼树的基本步骤如下:

  1. 统计每个字符的出现频率
  2. 将每个字符视为一个节点,频率作为权值
  3. 每次从节点集合中选取权值最小的两个节点合并
  4. 将合并后的新节点(权值为两节点之和)加入集合
  5. 重复上述过程直到只剩一个节点,形成完整的哈夫曼树

编码生成规则:从根节点出发,向左子树走记为0,向右子树走记为1,到达叶节点时的路径即为该字符的编码。

2. C++ 实现的关键数据结构

2.1 哈夫曼树节点设计

我们首先需要定义哈夫曼树的节点结构。每个节点需要存储以下信息:

struct HuffmanNode { char data; // 字符数据(叶节点有效) unsigned freq; // 字符频率/权值 HuffmanNode *left; // 左子节点指针 HuffmanNode *right; // 右子节点指针 // 构造函数 HuffmanNode(char data, unsigned freq) : data(data), freq(freq), left(nullptr), right(nullptr) {} };

2.2 优先队列的比较函数

STL的priority_queue默认实现最大堆,而我们需要最小堆。可以通过自定义比较函数实现:

struct Compare { bool operator()(HuffmanNode* l, HuffmanNode* r) { return l->freq > r->freq; // 小顶堆 } }; // 优先队列类型定义 using MinHeap = priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare>;

3. 完整实现步骤

3.1 构建哈夫曼树

HuffmanNode* buildHuffmanTree(const unordered_map<char, unsigned>& freqMap) { MinHeap minHeap; // 1. 创建叶节点并加入优先队列 for (auto& pair : freqMap) { minHeap.push(new HuffmanNode(pair.first, pair.second)); } // 2. 循环合并节点直到只剩一个 while (minHeap.size() != 1) { // 取出两个最小频率节点 HuffmanNode* left = minHeap.top(); minHeap.pop(); HuffmanNode* right = minHeap.top(); minHeap.pop(); // 创建新内部节点('$'标记内部节点) HuffmanNode* top = new HuffmanNode('$', left->freq + right->freq); top->left = left; top->right = right; minHeap.push(top); } return minHeap.top(); // 返回根节点 }

3.2 生成编码表

通过深度优先遍历(DFS)生成每个字符的编码:

void generateCodes(HuffmanNode* root, const string& str, unordered_map<char, string>& huffmanCode) { if (!root) return; // 叶节点存储编码 if (!root->left && !root->right) { huffmanCode[root->data] = str; } generateCodes(root->left, str + "0", huffmanCode); generateCodes(root->right, str + "1", huffmanCode); }

3.3 编码字符串

利用生成的编码表对输入字符串进行编码:

string encodeString(const string& input, const unordered_map<char, string>& huffmanCode) { string encoded; for (char ch : input) { encoded += huffmanCode.at(ch); } return encoded; }

3.4 解码字符串

根据哈夫曼树解码二进制串:

string decodeString(HuffmanNode* root, const string& encoded) { string decoded; HuffmanNode* curr = root; for (char bit : encoded) { curr = (bit == '0') ? curr->left : curr->right; // 到达叶节点 if (!curr->left && !curr->right) { decoded += curr->data; curr = root; } } return decoded; }

4. 完整可运行示例

下面是一个处理6个字符(a-f)的完整示例程序:

#include <iostream> #include <queue> #include <unordered_map> #include <string> using namespace std; // 哈夫曼节点结构 struct HuffmanNode { char data; unsigned freq; HuffmanNode *left, *right; HuffmanNode(char data, unsigned freq) : data(data), freq(freq), left(nullptr), right(nullptr) {} }; // 优先队列比较函数 struct Compare { bool operator()(HuffmanNode* l, HuffmanNode* r) { return l->freq > r->freq; } }; using MinHeap = priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare>; // 构建哈夫曼树 HuffmanNode* buildHuffmanTree(const unordered_map<char, unsigned>& freqMap) { MinHeap minHeap; for (auto& pair : freqMap) { minHeap.push(new HuffmanNode(pair.first, pair.second)); } while (minHeap.size() != 1) { HuffmanNode* left = minHeap.top(); minHeap.pop(); HuffmanNode* right = minHeap.top(); minHeap.pop(); HuffmanNode* top = new HuffmanNode('$', left->freq + right->freq); top->left = left; top->right = right; minHeap.push(top); } return minHeap.top(); } // 生成编码表 void generateCodes(HuffmanNode* root, const string& str, unordered_map<char, string>& huffmanCode) { if (!root) return; if (!root->left && !root->right) { huffmanCode[root->data] = str; } generateCodes(root->left, str + "0", huffmanCode); generateCodes(root->right, str + "1", huffmanCode); } // 编码字符串 string encodeString(const string& input, const unordered_map<char, string>& huffmanCode) { string encoded; for (char ch : input) { encoded += huffmanCode.at(ch); } return encoded; } // 解码字符串 string decodeString(HuffmanNode* root, const string& encoded) { string decoded; HuffmanNode* curr = root; for (char bit : encoded) { curr = (bit == '0') ? curr->left : curr->right; if (!curr->left && !curr->right) { decoded += curr->data; curr = root; } } return decoded; } int main() { // 示例:6个字符的频率 unordered_map<char, unsigned> freqMap = { {'a', 6}, {'b', 3}, {'c', 8}, {'d', 2}, {'e', 10}, {'f', 4} }; // 1. 构建哈夫曼树 HuffmanNode* root = buildHuffmanTree(freqMap); // 2. 生成编码表 unordered_map<char, string> huffmanCode; generateCodes(root, "", huffmanCode); cout << "Huffman Codes:\n"; for (auto& pair : huffmanCode) { cout << pair.first << ": " << pair.second << endl; } // 3. 编码示例字符串 string testStr = "abcdef"; string encoded = encodeString(testStr, huffmanCode); cout << "\nOriginal string: " << testStr << endl; cout << "Encoded string: " << encoded << endl; // 4. 解码验证 string decoded = decodeString(root, encoded); cout << "Decoded string: " << decoded << endl; // 计算压缩率 int originalBits = testStr.length() * 8; // 假设原始ASCII编码 int compressedBits = encoded.length(); double ratio = 100.0 * (originalBits - compressedBits) / originalBits; cout << "\nCompression ratio: " << ratio << "%" << endl; return 0; }

5. 性能分析与优化

5.1 时间复杂度分析

操作时间复杂度说明
构建优先队列O(n log n)n为不同字符数量
构建哈夫曼树O(n log n)每次堆操作O(log n),共n-1次
生成编码表O(n)树遍历
编码字符串O(m)m为输入字符串长度
解码字符串O(k)k为编码后二进制串长度

5.2 空间优化技巧

  1. 内存管理:示例代码中未释放节点内存,实际应用中应添加析构函数递归删除树节点
  2. 位操作优化:编码结果可使用bitset而非字符串存储,节省空间
  3. 频率表压缩:对于大型文件,可使用更紧凑的频率表表示方法

5.3 扩展功能

// 添加内存释放函数 void deleteTree(HuffmanNode* root) { if (!root) return; deleteTree(root->left); deleteTree(root->right); delete root; } // 在main函数结束前调用 deleteTree(root);

6. 实际应用与测试

6.1 测试用例设计

考虑以下测试场景:

  1. 常规情况

    unordered_map<char, unsigned> freqMap1 = { {'a', 5}, {'b', 9}, {'c', 12}, {'d', 13}, {'e', 16}, {'f', 45} };
  2. 极端情况

    // 所有字符频率相同 unordered_map<char, unsigned> freqMap2 = { {'a', 1}, {'b', 1}, {'c', 1}, {'d', 1}, {'e', 1}, {'f', 1} }; // 一个字符占绝对多数 unordered_map<char, unsigned> freqMap3 = { {'a', 99}, {'b', 1}, {'c', 1}, {'d', 1}, {'e', 1}, {'f', 1} };

6.2 编码结果验证

对于频率表freqMap1,程序输出示例:

Huffman Codes: f: 0 c: 100 d: 101 a: 1100 b: 1101 e: 111 Original string: abcdef Encoded string: 11001101111001010 Decoded string: abcdef Compression ratio: 42.1875%

6.3 边界条件处理

在实际应用中需要考虑:

  1. 空输入处理
  2. 单字符重复情况
  3. 非ASCII字符支持
  4. 大文件分块处理
// 空输入检查示例 if (freqMap.empty()) { cerr << "Error: Empty frequency map!" << endl; return nullptr; }

7. 进阶实现技巧

7.1 使用更高效的数据结构

对于大规模数据,可以考虑以下优化:

  1. 使用数组存储哈夫曼树:对于已知字符集大小的情况,可以用数组而非指针表示树结构
  2. 位级操作:使用位运算而非字符串拼接来提高编码效率

7.2 并行化处理

对于超大文件,可以:

  1. 分块统计字符频率
  2. 并行构建哈夫曼树
  3. 多线程进行编码操作

7.3 文件压缩实战

将上述算法扩展为实际文件压缩工具需要:

  1. 添加文件IO处理
  2. 序列化哈夫曼树结构
  3. 处理二进制数据而非文本
  4. 添加压缩文件头信息
// 简化的文件头结构示例 struct FileHeader { uint32_t magicNumber = 0x484D414E; // "HMAN" uint32_t originalSize; uint32_t tableSize; // 后面跟着频率表和编码数据 };

通过本实现的完整哈夫曼编码方案,开发者可以深入理解数据压缩的核心原理,并将其应用于实际项目中。代码中使用的优先队列方案既保持了清晰性,又具有良好的性能表现,是学习算法与数据结构结合的优秀范例。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询