文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

C++哈夫曼树的概念是什么与怎么实现

2023-06-30 10:00

关注

这篇文章主要介绍“C++哈夫曼树的概念是什么与怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“C++哈夫曼树的概念是什么与怎么实现”文章能帮助大家解决问题。

一、 基本概念

结点的权: 有某种现实含义的数值

结点的带权路径长度: 从结点的根到该结点的路径长度与该结点权值的乘积

树的带权路径长度: 树上所有叶结点的带权路径长度之和C++哈夫曼树的概念是什么与怎么实现

哈夫曼树: 在含有 n n n个带权叶结点的二叉树中, w p l wpl wpl 最小 的二叉树称为哈夫曼树,也称最优二叉树(给定叶子结点,哈夫曼树不唯一)。

二、构造哈夫曼树

比较简单,此处不赘述步骤

C++哈夫曼树的概念是什么与怎么实现

C++哈夫曼树的概念是什么与怎么实现

三、哈夫曼树的基本性质

四、哈夫曼编码

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

在考试中,小渣利用哈夫曼编码老渣发电报传递100道选择题的答案,小渣传递了10个A、8个B、80个C、2个D,老渣利用哈夫曼编码的方式解码。

小渣构造的哈夫曼树如下:

C++哈夫曼树的概念是什么与怎么实现

可以发现,A、B、C、D的编码分别为10、111、0、110。

这样小渣只要根据1~100题的答案顺序发送01序列,老渣收到后进行解码就能正确收到答案了。而且哈夫曼编码的方式不会有歧义,因为哈夫曼编码是一种前缀编码。

前缀编码: 没有一个编码是另一个编码的前缀,因为数据节点都是叶子节点。如果出现一个字符的编码是另一个字符编码的前缀,那这个字符一定处于内部节点,这是不可能的

由哈夫曼树得到的哈夫曼编码: 字符集中的每个字符都是以叶子结点出现在哈夫曼树中,各个字符出现的频率为结点的权值。

给字符串进行编码的时候,由于出现频率越高(权值大)的字符距离根节点越进,编码越短;只有出现频率越低(权值小)的字符距离根节点较远,编码长。没关系,由于频率高的字符编码都短,所以哈夫曼编码可以得到最短的编码序列

C++哈夫曼树的概念是什么与怎么实现

五、哈夫曼解码

哈夫曼编码不同于ASCII和Unicode这些字符编码,这些字符集中的码长都采用的是长度相同的编码方案,而哈夫曼编码使用的是变长编码,而且哈夫曼编码满足立刻可解码性(就是说任一字符的编码都不会是另一个更长字符编码的前缀),只要当某个字符的编码中所有位被全部接收时,可以立即进行解码而无须等待后面接收的位来决定是否存在另一个合法的更长的编码

第一张表不满足立刻可解码性,第二张表满足

C++哈夫曼树的概念是什么与怎么实现

我们接收100的时候,需要考虑是立刻解码成D还是等待接收下一位,如果下一位是0就可以解码成B,这就说明表中的编码不具有立刻可解码性

C++哈夫曼树的概念是什么与怎么实现

第二张表就具有立刻可解码性,因为任一字符的编码都不是另一个更长字符编码的前缀。只要收到的序列对应了某个字符的编码,直接解码成对应字符即可,无需等待后面的数据

我们的代码实现是用字符串构建哈夫曼树,只能针对由该字符串包含的字符组成的字符串进行编解码。代码里使用字符串存储的编码,实际上应该用bit进行存储

#include <iostream>#include <string>#include <vector>#include <functional>#include <unordered_map>#include <queue>using namespace std;using uint = unsigned int;class HuffmanTree {public:    // 这里的lambda表达式用来初始化function函数对象    // priority_queue的构造函数指出,如果传入一个参数,那这个参数用来初始化比较器对象    // 如果传入两个参数,第一个是比较器对象,第二个是底层容器    HuffmanTree()        :min_heap_([](Node* n1, Node* n2)->bool {return n1->weight_ > n2->weight_; })        , root_(nullptr)    {}    ~HuffmanTree() {        init();        cout << "已释放所有内存!" << endl;    }    // 根据字符串创建哈夫曼树    void create(const string& str) {        if (root_ != nullptr) {            cout << "哈夫曼树初始化..." << endl;            init();            cout << "初始化完成!" << endl;        }        // 统计频率(权重)        unordered_map<char, uint> w_map;        for (char c : str) {            w_map[c]++;        }        // 遍历w_map,把所有的字符对应的权重放入小根堆,按照权重排序        for (pair<const char, uint>& p : w_map) {            min_heap_.push(new Node(p.first, p.second));        }        // 根据优先级队列,从小根堆中取出节点,构建哈夫曼树        while (min_heap_.size() > 1) {            Node* n1 = min_heap_.top();            min_heap_.pop();            Node* n2 = min_heap_.top();            min_heap_.pop();            Node* node = new Node('\0', n1->weight_ + n2->weight_);  // 内部节点存\0            node->left_ = n1;            node->right_ = n2;            min_heap_.push(node);        }        root_ = min_heap_.top();        min_heap_.pop();        // 创建完哈夫曼树,直接对传入的海量字符进行编码并存储到code_map_        create_huffman_code(str);    }    string get_code(const string& str) {        // 利用哈夫曼树对str编码并返回        string code;        for (char c : str) {            code += code_map_[c];        }        return code;    }    void show_huffman_code() const {        // 打印哈夫曼编码        for (const auto& pair : code_map_) {            cout << pair.first << " : " << pair.second << endl;        }    }    string decode(const string& encode_str) {        Node* cur = root_;        string decode_str;        for (char c : encode_str) {            if (c == '0') {                cur = cur->left_;            }            else {                cur = cur->right_;            }            if (cur->left_ == nullptr && cur->right_ == nullptr) {                // 到达叶子节点                decode_str.push_back(cur->data_);                cur = root_;            }        }        return decode_str;    }    uint get_wpl() {        if (root_ == nullptr) {            return 0;        }        if (root_->left_ == nullptr && root_->right_ == nullptr) {            // 对于叶子节点,直接返回自己的weight * depth            return root_->weight_ * 1;        }        else {            // 对于内部节点,直接返回从子节点拿到的weight之和            return get_w(root_->left_, 2) + get_w(root_->right_, 2);        }    }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 init() {        // 释放哈夫曼树的节点        if (root_ != nullptr) {            queue<Node*> q;            q.push(root_);            while (!q.empty()) {                Node* node = q.front();                q.pop();                if (node->left_ != nullptr) {                    q.push(node->left_);                }                if (node->right_ != nullptr) {                    q.push(node->right_);                }                delete node;            }            MinHeap empty([](Node* n1, Node* n2)->bool {return n1->weight_ > n2->weight_; });            swap(empty, min_heap_);            code_map_.clear();        }    }    void create_huffman_code(const string& str) {        string code;        create_huffman_code(root_, code);    }    void create_huffman_code(Node* node, string code) {        if (node->left_ == nullptr && node->right_ == nullptr) {            code_map_[node->data_] = code;            return;        }        create_huffman_code(node->left_, code + "0");        create_huffman_code(node->right_, code + "1");    }    uint get_w(Node* node, int depth) {        if (node == nullptr) {            return 0;        }        if (node->left_ == nullptr && node->right_ == nullptr) {            // 对于叶子节点,直接返回自己的weight * depth            return node->weight_ * depth;        }        else {            // 对于内部节点,直接返回从子节点拿到的weight之和            return get_w(node->left_, depth + 1) + get_w(node->right_, depth + 1);        }    }private:        Node* root_;    unordered_map<char, string> code_map_;  // 存储字符对应的哈夫曼编码    using MinHeap = priority_queue<Node*, vector<Node*>, function<bool(Node*, Node*)>>;    MinHeap min_heap_; // 构建哈夫曼树的时候使用小根堆};int main() {    string str = "Aa";    HuffmanTree htree;    htree.create(str);    htree.show_huffman_code();    cout << htree.get_wpl() << endl;    str = "ABC";    htree.create(str);    htree.show_huffman_code();    cout << htree.get_wpl() << endl;;    string encode = htree.get_code(str);    cout << "encode:" << encode << endl;    cout << "decode:" << htree.decode(encode) << endl;    return 0;}

六、文件的压缩和解压缩

我们利用哈夫曼编码压缩文件的时候,如果文件是100M,我们可以压缩成20M,如果文件时1K,我们可能压缩成2K,当文件较小的时候,我们得到的压缩文件反而更大了,这是为什么?

文件的压缩过程如下:

解码的过程如下:

由于压缩文件不仅存储01码,还需要存储文件字节数据以及权值用来重建哈夫曼树(就是代码中的w_map)。当原始文件较小时,文件字节数据以及权值可能大于原始文件的大小,故小文件压缩后可能变大

关于“C++哈夫曼树的概念是什么与怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯