doc/notebook/docs/data_structure/霍夫曼编码.md

164 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

霍夫曼编码Huffman Coding是一种广泛使用的数据压缩算法特别适用于无损数据压缩。它由David A. Huffman在1952年提出基于信息论中的熵编码思想。
## 霍夫曼编码的基本原理
霍夫曼编码是一种**前缀编码**,即没有任何一个编码是其他编码的前缀。它通过构建一个**最优二叉树**,使得频率越高的字符编码越短,从而减少整体编码的长度,实现数据压缩。
### 主要步骤
1. **统计字符频率**:计算待压缩数据中每个字符出现的频率。
2. **构建优先队列**:将每个字符和其对应的频率作为一个节点,放入优先队列(最小堆)。
3. **构建霍夫曼树**
- 从优先队列中取出两个频率最小的节点,作为左右子节点,构造一个新的父节点,父节点的频率为左右子节点频率之和。
- 将这个新的父节点插回优先队列中。
- 重复上述过程,直到队列中只剩下一个节点,这个节点就是霍夫曼树的根节点。
4. **生成编码**
- 从霍夫曼树的根节点出发,给左边的分支标记为`0`,右边的分支标记为`1`。
- 从根节点到每个叶子节点(即字符节点)路径上的`0`和`1`组合,构成该字符的霍夫曼编码。
### 示例
假设我们有如下字符及其出现频率:
```
字符 | 频率
---------------
A | 5
B | 9
C | 12
D | 13
E | 16
F | 45
```
#### 1. 统计频率
初始优先队列:
```
(A, 5), (B, 9), (C, 12), (D, 13), (E, 16), (F, 45)
```
#### 2. 构建霍夫曼树
- 取出频率最小的两个节点 `(A, 5)``(B, 9)`,构造一个新节点 `(AB, 14)`,插回队列。
- 队列更新为:`(AB, 14), (C, 12), (D, 13), (E, 16), (F, 45)`
- 取出 `(C, 12)``(D, 13)`,构造 `(CD, 25)`,插回队列。
- 队列更新为:`(AB, 14), (CD, 25), (E, 16), (F, 45)`
- 取出 `(AB, 14)``(E, 16)`,构造 `(ABE, 30)`,插回队列。
- 队列更新为:`(CD, 25), (ABE, 30), (F, 45)`
- 取出 `(CD, 25)``(ABE, 30)`,构造 `(CDEAB, 55)`,插回队列。
- 队列更新为:`(CDEAB, 55), (F, 45)`
- 最后将 `(CDEAB, 55)``(F, 45)` 合并为根节点 `(Root, 100)`
最终霍夫曼树:
```
[Root, 100]
/ \
[F, 45] [CDEAB, 55]
/ \
[CD, 25] [ABE, 30]
/ \ / \
[C,12][D,13][A,5] [B,9] [E,16]
```
#### 3. 生成编码
从根节点到每个字符的路径生成编码:
```
F: 0
C: 100
D: 101
A: 1100
B: 1101
E: 111
```
### 霍夫曼编码的优点
- **无损压缩**:霍夫曼编码不会丢失任何信息,解码后的数据与原始数据完全一致。
- **高效性**:对于频率分布差异较大的字符集,霍夫曼编码能显著减少编码后的数据量。
- **简单实现**:算法简单,适合软件实现。
### 霍夫曼编码的应用
- **文件压缩**如ZIP和RAR等文件压缩格式。
- **图像压缩**如JPEG图像格式的压缩。
- **数据传输**:在数据传输中减少带宽占用。
### 示例代码 (C++)
以下是一个简单的C++实现霍夫曼编码的示例:
```cpp
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;
// 定义霍夫曼树节点
struct HuffmanNode {
char data;
int freq;
HuffmanNode *left, *right;
HuffmanNode(char data, int freq) : data(data), freq(freq), left(NULL), right(NULL) {}
};
// 比较器,用于优先队列
struct compare {
bool operator()(HuffmanNode* l, HuffmanNode* r) {
return l->freq > r->freq;
}
};
// 打印霍夫曼编码
void printCodes(HuffmanNode* root, string str) {
if (!root) return;
if (root->data != '$') cout << root->data << ": " << str << "\n";
printCodes(root->left, str + "0");
printCodes(root->right, str + "1");
}
// 构建霍夫曼树并打印编码
void HuffmanCodes(char data[], int freq[], int size) {
HuffmanNode *left, *right, *top;
priority_queue<HuffmanNode*, vector<HuffmanNode*>, compare> minHeap;
for (int i = 0; i < size; ++i)
minHeap.push(new HuffmanNode(data[i], freq[i]));
while (minHeap.size() != 1) {
left = minHeap.top();
minHeap.pop();
right = minHeap.top();
minHeap.pop();
top = new HuffmanNode('$', left->freq + right->freq);
top->left = left;
top->right = right;
minHeap.push(top);
}
printCodes(minHeap.top(), "");
}
int main() {
char arr[] = { 'A', 'B', 'C', 'D', 'E', 'F' };
int freq[] = { 5, 9, 12, 13, 16, 45 };
int size = sizeof(arr) / sizeof(arr[0]);
HuffmanCodes(arr, freq, size);
return 0;
}
```
### 总结
霍夫曼编码是非常有效的无损压缩算法,特别适用于字符频率分布不均的情况。它的思想简单但非常实用,在许多领域得到了广泛应用。