哈夫曼树和哈夫曼编码

176 阅读3分钟

哈夫曼树

哈夫曼树又称为最佳判定树、最优二叉树,是一种带权路径长度的最短的二叉树,常用于数据压缩。

所谓树的带权路径长度就是树中所有的叶子节点的权值乘以其到根节点路径的长度。

哈夫曼编码

目的: 为给定的字符集合构建二进制编码,使得编码的期望长度达到最短。

哈夫曼树实现代码

#include <cstddef>
#include <iostream>
#include <iterator>
#include <unordered_map>
#include <string>
#include <queue>
#include <vector>
#include <functional>
using namespace std;

using uint = unsigned int;

// 实现哈夫曼树
class HuffmanTree {
public:
  HuffmanTree()
    : minHeap_([](Node* n1, Node* n2) -> bool {
      return n1->weight_ > n2->weight_;
    }) 
    , root_(nullptr) 
    {}

    // 创建Huffman树
    void create(string str) {
      // 先统计字符的权值
      unordered_map<char, uint> dataMap;

      for(char ch : str) {
        dataMap[ch]++;
      }

      // 生成节点,放入小根堆中
      for (auto& pair: dataMap) {
        minHeap_.push(new Node(pair.first, pair.second));
      }

      while (minHeap_.size() > 1) {

        // 获取两个权值最小的
        Node* n1 = minHeap_.top();
        minHeap_.pop();

        Node* n2 = minHeap_.top();
        minHeap_.pop();

        // 使用这两个权值最小的生成一个父节点
        Node* node = new Node('\0', n1->weight_ + n2->weight_);

        node->left_ = n1;
        node->right_ = n2;

        minHeap_.push(node);
      }

      root_ = minHeap_.top();  // 最后小根堆只剩一个元素,是Huffman树的根节点

    }

    // 输出Huffman编码
    void showHuffmanCode() {
      string code;
      showHuffmanCode(root_, code);

      for (auto& pair : codeMap_) {
        cout << pair.first << " : " << pair.second << endl; 
      }

      cout << endl;

    }

private:
  struct Node {

    Node(char data, uint weight)
        : data_(data)
        , weight_(weight)
        , left_(nullptr)
        , right_(nullptr)
        {}

    char data_;  // 字符数据
    uint weight_; // 节点的权值
    Node* left_;  // 指向左孩子节点
    Node* right_;  // 指向右孩子节点
  };

private:
  void showHuffmanCode(Node* root, string code) {
    // 前序遍历VLR  
    if (root->left_ == nullptr && root->right_ == nullptr) {
      codeMap_[root->data_] = code;
      return;
    }

    showHuffmanCode(root->left_, code + "0");  // 往左走,code 加 "0"
    showHuffmanCode(root->right_, code + "1");  // 往左走,code 加 "0"

  }

private:
  Node* root_;  // 指向根节点
  unordered_map<char, string> codeMap_;  // 存储字符对应的Huffman编码


  using MinHeap = priority_queue<Node *, vector<Node*>, function<bool(Node*, Node*)>>;
  MinHeap minHeap_;  // 小根堆
};

int main() {

  string str = "ABACDAEFDEG";
  HuffmanTree htree;

  htree.create(str);

  htree.showHuffmanCode();


  return 0;
}

运行结果为

D : 111
E : 110
A : 10
C : 011
F : 010
B : 001
G : 000

注意: 因为Huffman编码会有多种结果,所以会出现两次运行结果不一样的情况

图结构如下: 往右走是1,往左走是0

image.png

基于Huffman树实现数据的编码和解码

代码

#include <cstddef>
#include <iostream>
#include <iterator>
#include <unordered_map>
#include <string>
#include <queue>
#include <vector>
#include <functional>
using namespace std;

using uint = unsigned int;

// 实现哈夫曼树
class HuffmanTree {
public:
  HuffmanTree()
    : minHeap_([](Node* n1, Node* n2) -> bool {
      return n1->weight_ > n2->weight_;
    }) 
    , root_(nullptr) 
    {}

    // 创建Huffman树
    void create(string str) {
      // 先统计字符的权值
      unordered_map<char, uint> dataMap;

      for(char ch : str) {
        dataMap[ch]++;
      }

      // 生成节点,放入小根堆中
      for (auto& pair: dataMap) {
        minHeap_.push(new Node(pair.first, pair.second));
      }

      while (minHeap_.size() > 1) {

        // 获取两个权值最小的
        Node* n1 = minHeap_.top();
        minHeap_.pop();

        Node* n2 = minHeap_.top();
        minHeap_.pop();

        // 使用这两个权值最小的生成一个父节点
        Node* node = new Node('\0', n1->weight_ + n2->weight_);

        node->left_ = n1;
        node->right_ = n2;

        minHeap_.push(node);
      }

      root_ = minHeap_.top();  // 最后小根堆只剩一个元素,是Huffman树的根节点

    }

    // 输出Huffman编码
    void showHuffmanCode() {
      string code;
      showHuffmanCode(root_, code);

      for (auto& pair : codeMap_) {
        cout << pair.first << " : " << pair.second << endl; 
      }

      cout << endl;

    }

    // 编码
    string encode(string str) {
      string encode_str;
      for (char ch: str) {
        encode_str.append(codeMap_[ch]);
      }

      return encode_str;
    }


    // 解码
    string decode(string encode) {
      string decode_str;
      Node* cur = root_;

      for (char ch : encode) {
        if (ch == '0') {
          cur = cur->left_; // 等于0往左走
        } else {
          cur = cur->right_;  // 等于1往右走
        }

        if (cur->left_ == nullptr && cur->right_ == nullptr) {
          // 到达叶子节点了
          decode_str.push_back(cur->data_);
          cur = root_;  // 从头找下一个
        }
      }

      return decode_str;
    }



private:
  struct Node {

    Node(char data, uint weight)
        : data_(data)
        , weight_(weight)
        , left_(nullptr)
        , right_(nullptr)
        {}

    char data_;  // 字符数据
    uint weight_; // 节点的权值
    Node* left_;  // 指向左孩子节点
    Node* right_;  // 指向右孩子节点
  };

private:
  void showHuffmanCode(Node* root, string code) {
    // 前序遍历VLR  
    if (root->left_ == nullptr && root->right_ == nullptr) {
      codeMap_[root->data_] = code;
      return;
    }

    showHuffmanCode(root->left_, code + "0");  // 往左走,code 加 "0"
    showHuffmanCode(root->right_, code + "1");  // 往左走,code 加 "0"

  }

private:
  Node* root_;  // 指向根节点
  unordered_map<char, string> codeMap_;  // 存储字符对应的Huffman编码


  using MinHeap = priority_queue<Node *, vector<Node*>, function<bool(Node*, Node*)>>;
  MinHeap minHeap_;  // 小根堆
};

int main() {

  string str = "ABACDAEFDEG";
  HuffmanTree htree;

  htree.create(str);

  htree.showHuffmanCode();

  string encode = htree.encode(str);

  cout << "encode:" << encode << endl;

  string decode = htree.decode(encode);

  cout << "decode:" <<decode << endl;


  return 0;
}

测试

➜  build git:(main) ✗ ./HuffmanTree  
D : 111
E : 110
A : 10
C : 011
F : 010
B : 001
G : 000

encode:100011001111110110010111110000
decode:ABACDAEFDEG