Huffman树及其应用

首先需要介绍一个概念:

等长编码

  • 计算机二进制编码
    • ASCII 码
    • 中文编码
  • 等长编码
    • 假设所有编码都等长
    • 表示 n 个不同的字符需要 log2n位
    • 字符的使用频率相等
  • 空间效率
    • 对于使用频率不同的字符,给频率低的字符等长编码,空间效率就不高

数据压缩和不等长编码

可以利用字符的出现频率来编码

  • 经常出现的字符的编码较短,不常出现的字符编码较长

数据压缩既能节省磁盘空间,又能提高运算速度(外存时空权衡的规则)

对于编码实现,我们可以使用前缀编码

前缀编码

  • 任何一个字符的编码都不是另外一个 字符编码的前缀
  • 这种前缀特性保证了代码串被反编码时,不会有多种可能
  • 若编码为Z(00), K(01), F(11), C(0), U(1), D(10), L(110), E(010) 。 则对应:”ZKD”,”CCCUUC”等多种可能,这就不是前缀编码

Huffman树与前缀编码

Huffman编码将代码与字符相联系

  • 不等长编码
  • 代码长度取决于对应字符的相对使用频率或“权”
  • 逻辑结构上是扩充二叉树

建立Huffman编码树

定义

对于n个字符K0,K1,...,Kn-1,它们的使用频率分别为w0, w1,...,wn-1,给出它们的前缀编码,使得总编码效率最高。

  • 给出一个具有n个外部结点的扩充二叉树
    • 该二叉树每个外部结点 Ki 有一个权 wi外部路径长度为li
    • 权越大的叶结点离根越近

可以看出:

  • 只有在外部节点保存了实际信息
  • 频率越大其编码越短

编码过程

  • 首先,按照“权”(例如频率)将字符排为一列
    • 接着,拿走前两个字符(“权”最小的两个字符)
    • 再将它们标记为Huffman树的树叶,将这两个树叶标为一个分支结点的两个孩子,而该结点的权即为两树叶的权之和
  • 将所得“权”放回序列,使“权”的顺序保持
  • 重复上述步骤直至序列处理完毕

译码: 从左至右逐位判别代码串, 直至确定一个字符

与编码过程相逆

  • 从树的根结点开始
    • “0”下降到左分支
    • “1”下降到右分支
    • 到达一个树叶结点,对应的字符就是文本信息的字符 连续译码
  • 译出了一个字符,再回到树根,从二进制位串中的下一位开始继续译码

Huffman树ADT

template <class T> class HuffmanTree {
private:
    HuffmanTreeNode<T>* root;//Huffman树的树根 
    void MergeTree(HuffmanTreeNode<T> &ht1, HuffmanTreeNode<T> &ht2, HuffmanTreeNode<T>*parent);//把ht1和ht2为根的合并成一棵以parent为根的Huffman子树
public: 
    HuffmanTree(T weight[],int n);//构造Huffman树,weight是存储权值的数组,n是数组长度 
    virtual ~HuffmanTree(){DeleteTree(root);}; //析构函数
} 
复制代码

Huffman树的构造

template<class T>
HuffmanTree<T>::HuffmanTree(T weight[], int n) {
    MinHeap<HuffmanTreeNode<T>> heap;
    HuffmanTreeNode<T> *parent,&left,&right;
    HuffmanTreeNode<T> *NodeList = new HuffmanTreeNode<T>[n];

    for(int i=0;i<n;i++){
        NodeList[i].element = weight[i];
        NodeList[i].parent = NodeList[i].left = NodeList.right = NULL;
        heap.Insert(NodeList[i]);
    }
    for(i=0;i<n-1;i++){
        parent = new HuffmanTreeNode<T>;
        left = heap.removeMin();
        right = heap.removeMin();
        MergeTree(left,right,parent); //合并两颗最小子树
        heap.Insert(*parent);
        root = parent;  //建立根节点
    }
    delete [] NodeList;
}
复制代码

Huffman树编码效率

  • 估计Huffman编码所节省的空间
  • 平均每个字符的代码长度等于每个代码的长度 ci 乘以其出现的概率 pi ,即: c0p0 + c1p1 + ... + cn-1pn-1 或 (c0f0 + c1f1 + ... + cn-1fn-1) / fT 这里fi为第i个字符的出现频率,而fT为所有字符出现的 总次数

总之,概率分布越不均匀,压缩比越高;最差的压缩比与等长编码一样。 实际效率分析,参考这篇文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是C++实现哈夫曼编码及其应用的代码: ```c++ #include <iostream> #include <queue> #include <unordered_map> #include <vector> using namespace std; // 定义哈夫曼的结点 struct HuffmanNode { char data; // 数据 int freq; // 频率 HuffmanNode* left; // 左子结点 HuffmanNode* right; // 右子结点 HuffmanNode(char _data, int _freq) { data = _data; freq = _freq; left = nullptr; right = nullptr; } }; // 小根堆的比较函数 struct Compare { bool operator()(HuffmanNode* a, HuffmanNode* b) const { return a->freq > b->freq; } }; // 构造哈夫曼 HuffmanNode* buildHuffmanTree(const unordered_map<char, int>& freqMap) { priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare> minHeap; for (auto& p : freqMap) { minHeap.push(new HuffmanNode(p.first, p.second)); } while (minHeap.size() > 1) { HuffmanNode* left = minHeap.top(); minHeap.pop(); HuffmanNode* right = minHeap.top(); minHeap.pop(); HuffmanNode* parent = new HuffmanNode('#', left->freq + right->freq); parent->left = left; parent->right = right; minHeap.push(parent); } return minHeap.top(); } // 生成哈夫曼编码 void generateHuffmanCode(HuffmanNode* root, string code, unordered_map<char, string>& codeMap) { if (root == nullptr) { return; } if (root->left == nullptr && root->right == nullptr) { codeMap[root->data] = code; return; } generateHuffmanCode(root->left, code + "0", codeMap); generateHuffmanCode(root->right, code + "1", codeMap); } // 压缩字符串 string compress(const string& str, const unordered_map<char, string>& codeMap) { string compressedStr = ""; for (char c : str) { compressedStr += codeMap.at(c); } return compressedStr; } // 解压缩字符串 string decompress(const string& compressedStr, HuffmanNode* root) { string decompressedStr = ""; HuffmanNode* p = root; for (char c : compressedStr) { if (c == '0') { p = p->left; } else { p = p->right; } if (p->left == nullptr && p->right == nullptr) { decompressedStr += p->data; p = root; } } return decompressedStr; } int main() { string str = "hello world"; unordered_map<char, int> freqMap; for (char c : str) { freqMap[c]++; } HuffmanNode* root = buildHuffmanTree(freqMap); unordered_map<char, string> codeMap; generateHuffmanCode(root, "", codeMap); string compressedStr = compress(str, codeMap); string decompressedStr = decompress(compressedStr, root); cout << "原始字符串: " << str << endl; cout << "压缩后的字符串: " << compressedStr << endl; cout << "解压缩后的字符串: " << decompressedStr << endl; return 0; } ``` 代码思路: 1. 首先统计输入字符串中每个字符的频率,存储在unordered_map<char, int> freqMap中。 2. 根据freqMap构造哈夫曼。 3. 通过哈夫曼生成每个字符的编码,存储在unordered_map<char, string> codeMap中。 4. 压缩输入字符串,将每个字符替换成其对应的编码。 5. 解压缩压缩后的字符串,将编码替换成对应的字符。 代码解释: 1. 定义了一个HuffmanNode结构体,用于表示哈夫曼的结点,其中包括了数据、频率、左子结点和右子结点。 2. 定义了一个小根堆的比较函数Compare,用于在构造哈夫曼时作为priority_queue的比较函数。 3. buildHuffmanTree函数用于构造哈夫曼。首先将freqMap中的每个字符和其对应的频率存储到小根堆minHeap中。然后每次弹出minHeap中频率最小的两个结点,将其合并成一个新的结点,并将新结点插入到minHeap中。重复上述步骤,直到minHeap中只剩下一个结点,即哈夫曼的根节点。 4. generateHuffmanCode函数用于生成哈夫曼编码。采用递归的方式遍历哈夫曼,对于每个叶子结点,将其对应的编码存储到codeMap中。具体实现中,对于每个结点,如果其左子结点和右子结点都为nullptr,则说明该结点是叶子结点,将其对应的编码存储到codeMap中;否则,依次处理其左子结点和右子结点,将其编码分别加上“0”和“1”。 5. compress函数用于压缩输入字符串。遍历输入字符串中的每个字符,将其对应的编码依次拼接到压缩后的字符串中。 6. decompress函数用于解压缩压缩后的字符串。遍历压缩后的字符串中的每个字符,依次沿着哈夫曼的路径进行移动,直到到达叶子结点,将对应的字符存储到解压缩后的字符串中。 输出结果: ``` 原始字符串: hello world 压缩后的字符串: 1101110110010011010101101100000001010101 解压缩后的字符串: hello world ``` 可以看到,经过压缩和解压缩后,原始字符串没有发生变化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值