哈夫曼树实现与编码生成

哈夫曼树简介与编码实例

哈夫曼树简介

哈夫曼树(Huffman Tree)是一种带权路径长度最短的二叉树,由David A. Huffman于1952年提出,广泛应用于数据压缩领域。

基本概念

  1. 路径长度:从根节点到某节点的路径上的边数
  2. 节点的带权路径长度:节点权值 × 路径长度
  3. 树的带权路径长度(WPL):所有叶子节点的带权路径长度之和

构建原则

  • 频率高的字符使用短编码
  • 频率低的字符使用长编码
  • 任一字符的编码都不是其他字符编码的前缀(前缀编码性质)

构建步骤

  1. 将每个数据看作一个单独的树,组成森林
  2. 选择权值最小的两棵树合并,新树根节点权值为子树权值之和
  3. 重复步骤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

构建过程:

  1. 排序:[5,9,12,13,16,45]
  2. 合并5和9 → 新节点14
  3. 合并12和13 → 新节点25
  4. 合并14和16 → 新节点30
  5. 合并25和30 → 新节点55
  6. 合并45和55 → 根节点100

步骤2:分配编码

从根节点开始,向左为0,向右为1:

频率 编码
5 5 1100
9 9 1101
12 12 100
13 13 101
16 16 111
45 45 0

哈夫曼编码的特点

  1. 最优前缀编码:没有任何编码是其他编码的前缀
  2. 贪心算法应用:每次合并两个频率最小的节点
  3. 压缩效率:对于非均匀分布数据压缩效果显著
  4. 唯一性:相同频率可能产生不同结构的树,但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位固定编码)
posted @ 2025-08-13 14:43  doctordragon666  阅读(75)  评论(0)    收藏  举报