哈夫曼树实现与编码生成
哈夫曼树简介与编码实例
哈夫曼树简介
哈夫曼树(Huffman Tree)是一种带权路径长度最短的二叉树,由David A. Huffman于1952年提出,广泛应用于数据压缩领域。
基本概念
- 路径长度:从根节点到某节点的路径上的边数
- 节点的带权路径长度:节点权值 × 路径长度
- 树的带权路径长度(WPL):所有叶子节点的带权路径长度之和
构建原则
- 频率高的字符使用短编码
- 频率低的字符使用长编码
- 任一字符的编码都不是其他字符编码的前缀(前缀编码性质)
构建步骤
- 将每个数据看作一个单独的树,组成森林
- 选择权值最小的两棵树合并,新树根节点权值为子树权值之和
- 重复步骤2,直到只剩一棵树
给定数据的哈夫曼编码
给定频率数组:vector<int> frequent = {5, 9, 12, 13, 45, 16}
步骤1:构建哈夫曼树
graph TD
A[100] --> B[45]
A --> C[55]
C --> D[25]
C --> E[30]
D --> F[12]
D --> G[13]
E --> H[14]
E --> I[16]
H --> J[5]
H --> K[9]
style B fill:#f9f
style F fill:#f9f
style G fill:#f9f
style J fill:#f9f
style K fill:#f9f
style I fill:#f9f
构建过程:
- 排序:[5,9,12,13,16,45]
- 合并5和9 → 新节点14
- 合并12和13 → 新节点25
- 合并14和16 → 新节点30
- 合并25和30 → 新节点55
- 合并45和55 → 根节点100
步骤2:分配编码
从根节点开始,向左为0,向右为1:
值 | 频率 | 编码 |
---|---|---|
5 | 5 | 1100 |
9 | 9 | 1101 |
12 | 12 | 100 |
13 | 13 | 101 |
16 | 16 | 111 |
45 | 45 | 0 |
哈夫曼编码的特点
- 最优前缀编码:没有任何编码是其他编码的前缀
- 贪心算法应用:每次合并两个频率最小的节点
- 压缩效率:对于非均匀分布数据压缩效果显著
- 唯一性:相同频率可能产生不同结构的树,但WPL相同
此哈夫曼树编码可以有效地压缩原始数据,高频数据(如45)获得了最短的编码(0),而低频数据(如5)获得了较长的编码(111)。
代码实现(C++实现)
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <unordered_map>
#include <iomanip>
using namespace std;
struct Node {
int value; // 存储原始值(叶子节点)
int freq; // 存储频率
Node* left;
Node* right;
// 叶子节点构造函数
Node(int v, int f) : value(v), freq(f), left(nullptr), right(nullptr) {}
// 内部节点构造函数
Node(int f, Node* l, Node* r) : value(-1), freq(f), left(l), right(r) {}
};
// 自定义比较函数(小顶堆)
struct CompareNode {
bool operator()(Node* a, Node* b) {
return a->freq > b->freq; // 注意:大于号实现小顶堆
}
};
// 打印树结构
void printTree(Node* node, int depth = 0) {
if (!node) return;
string indent(depth * 2, ' ');
if (node->value != -1) {
cout << indent << "Leaf: " << setw(2) << node->value
<< " (freq: " << setw(2) << node->freq << ")" << endl;
} else {
cout << indent << "Node: " << setw(2) << node->freq << endl;
}
printTree(node->left, depth + 1);
printTree(node->right, depth + 1);
}
// 生成哈夫曼编码
void generateCodes(Node* root, string code, unordered_map<int, string>& codes) {
if (!root) return;
if (root->value != -1) { // 叶子节点
codes[root->value] = code;
return;
}
generateCodes(root->left, code + "0", codes);
generateCodes(root->right, code + "1", codes);
}
// 计算带权路径长度(WPL)
int calculateWPL(Node* root, int depth = 0) {
if (!root) return 0;
if (root->value != -1) { // 叶子节点
return root->freq * depth;
}
return calculateWPL(root->left, depth + 1) +
calculateWPL(root->right, depth + 1);
}
// 释放树内存
void deleteTree(Node* root) {
if (!root) return;
deleteTree(root->left);
deleteTree(root->right);
delete root;
}
// 哈夫曼树构建与编码
void buildHuffmanTree(vector<int> freqs) {
// 使用小顶堆优先队列
priority_queue<Node*, vector<Node*>, CompareNode> minHeap;
// 创建叶子节点
for (int f : freqs) {
minHeap.push(new Node(f, f)); // 存储原始值
}
// 构建哈夫曼树
while (minHeap.size() > 1) {
Node* left = minHeap.top();
minHeap.pop();
Node* right = minHeap.top();
minHeap.pop();
Node* parent = new Node(left->freq + right->freq, left, right);
minHeap.push(parent);
}
Node* root = minHeap.top();
// 打印树结构
cout << "哈夫曼树结构:" << endl;
printTree(root);
cout << endl;
// 生成哈夫曼编码
unordered_map<int, string> huffmanCodes;
generateCodes(root, "", huffmanCodes);
// 计算WPL
int wpl = calculateWPL(root);
// 输出结果
cout << "哈夫曼编码表:" << endl;
cout << "┌───────┬───────┬──────────────┐" << endl;
cout << "│ 值 │ 频率 │ 编码 │" << endl;
cout << "├───────┼───────┼──────────────┤" << endl;
for (auto& pair : huffmanCodes) {
cout << "│ " << setw(5) << pair.first << " │ "
<< setw(5) << pair.first << " │ "
<< setw(12) << pair.second << " │" << endl;
}
cout << "└───────┴───────┴──────────────┘" << endl;
cout << "\n带权路径长度(WPL): " << wpl << endl;
cout << "压缩效率: " << fixed << setprecision(2)
<< (static_cast<double>(wpl) / (root->freq * 8)) * 100
<< "% (相比8位固定编码)" << endl;
// 释放内存
deleteTree(root);
}
int main() {
vector<int> frequent = {5, 9, 12, 13, 16, 45};
cout << "原始数据频率:" << endl;
cout << "┌───────┬───────┐" << endl;
cout << "│ 值 │ 频率 │" << endl;
cout << "├───────┼───────┤" << endl;
for (int f : frequent) {
cout << "│ " << setw(5) << f << " │ "
<< setw(5) << f << " │" << endl;
}
cout << "└───────┴───────┘" << endl << endl;
buildHuffmanTree(frequent);
return 0;
}
运行结果
原始数据频率:
┌───────┬───────┐
│ 值 │ 频率 │
├───────┼───────┤
│ 5 │ 5 │
│ 9 │ 9 │
│ 12 │ 12 │
│ 13 │ 13 │
│ 16 │ 16 │
│ 45 │ 45 │
└───────┴───────┘
哈夫曼树结构:
Node: 100
Leaf: 45 (freq: 45)
Node: 55
Node: 25
Leaf: 12 (freq: 12)
Leaf: 13 (freq: 13)
Node: 30
Node: 14
Leaf: 5 (freq: 5)
Leaf: 9 (freq: 9)
Leaf: 16 (freq: 16)
哈夫曼编码表:
┌───────┬───────┬──────────────┐
│ 值 │ 频率 │ 编码 │
├───────┼───────┼──────────────┤
│ 5 │ 5 │ 1100 │
│ 13 │ 13 │ 101 │
│ 45 │ 45 │ 0 │
│ 12 │ 12 │ 100 │
│ 9 │ 9 │ 1101 │
│ 16 │ 16 │ 111 │
└───────┴───────┴──────────────┘
带权路径长度(WPL): 224
压缩效率: 28.00% (相比8位固定编码)
本文来自博客园,作者:doctordragon666,转载请注明原文链接:https://www.cnblogs.com/riverstream/p/-/huffman