Huffman树的实现

8 篇文章 0 订阅

最近复习的时候遇见了哈夫曼编码,虽然在数据结构和离散数学课上都见识到了这个编码树,不过今天时第一次动手写哈夫曼树,借助了c++的stl写起来还是比较简单的。话不多说,现在开始。

1. 统计字符频率

统计字符这个还是很简单的,首先我们要打开要处理的文件,

    // 输入的文件名
    std::string file_name;
    std::cout << "Please input file name:";
    // 获取一行
    std::getline(std::cin, file_name);
    std::fstream file(file_name, std::ios_base::in);

我使用字典存放所读到的字符及其频数。

 if (file.is_open()) {
        // 未到文件尾,继续读
        while (!file.eof()) {
            // 输入
            file >> ch;
            // 搜索一下在不在里面,其实map的话不在里面int会初始为0,不管也行
            //if (map.find(ch) != map.end()) {
                //++map[ch];
            //}
            //else {
            //    map[ch] = 1;
            //}
            ++map[ch];
            ++sum;
        }
        file.close();
    }
    // 打开错误了
    else {
        std::cerr << "Open file error.\n";
        exit(-1);
    }

下面定义存放节点的数据类型定义

struct Node {
    // 存储的字符
    char ch;
    // 出现频率
    double frequency;
    // 左右指针
    Node *left = nullptr, *right = nullptr;

    // 默认建立的是最大堆,需要修改一下小于号的定义
    friend bool operator<(const Node &a, const Node &b) {
        return a.frequency > b.frequency;
    }

    // 构造函数
    Node() = delete;
    //使用字符和频率构造
    Node(char ch, double frequency) :ch(ch), frequency(frequency) {}
    // 只有频率,在合并中会使用
    Node(double frequency) : ch('\0'), frequency(frequency) {}
    // 拷贝构造,在new新节点时使用
    Node(const Node &node) : ch(node.ch), frequency(node.frequency),
        left(node.left), right(node.right) {}
}

2. 将得到的数据都转换为节点存放到优先队列中

    // 注意此迭代器可改变元素,建议使用const iter
    auto iter = map.cbegin();
    auto end = map.cend();
    // 优先队列,懒得自己维护红黑树(其实是因为我不会红黑树)
    std::priority_queue<Node>queue;
    // 注意map的底层是红黑树,在遍历时很可能时按照ASCII码的顺序打印的
    while (iter != end) {
        // 将node的相关信息弄进去
        queue.push(Node((*iter).first, (*iter).second * 1.0 / sum));
        // 输出一下吧
        std::cout << "'" << (*iter).first << "': " << "times: " << (*iter).second << "\n";
        // 迭代器别忘了往下挪
        ++iter;
    }

3. 根据哈夫曼树定义建树

    // 当剩余不止一个节点时
    while (queue.size() > 1) {
        // 此处使用指针,在最开始的时候会炸掉原来是因为自动分配的node在下一轮循环时会被释放掉
        // 我勒个去,取得的时局部变量的指针,当然会炸了,还是Java有GC好
        auto left = new Node(queue.top());
        queue.pop();
        auto right = new Node(queue.top());
        queue.pop();
        // 建一个新的节点,放进去
        Node node(left->frequency + right->frequency);
        // 左右指针赋值
        node.left = left;
        node.right = right;
        // 得到的节点再加进去
        queue.push(node);
    }

4. 建树完毕开始建立编码

    // 最后一个节点,也就是HuffmanTree的根啦
    auto top = new Node(queue.top());
    // 弹出去,相当于清空了,我在想要不要把new的空间释放掉
    queue.pop();
    // 遍历时得到编码,没有存储的需要的话就直接打印吧
    travelTree(top, "");

这个就是那个travelTree的代码

// 遍历树,
void travelTree(const Node *node, const std::string &code) {
    // 问题是Huffman树是正则二元树,这个判断没啥用
    /*if (node == nullptr) {
        return;
    }*/

    // 左右为空是树叶,就是有字符的地方
    if (node->left == nullptr && node->right == nullptr) {
        // 输出字符和频率
        std::cout << "'" << node->ch << "': " << code << "\n";
        //std::cout << "Frequency: " << std::setprecision(10) << node->frequency << "\n";
    }
    else {
        // 内点,继续走
        travelTree(node->left, code + '0');
        travelTree(node->right, code + '1');
    }
}

5. 将树释放掉

    // 释放树
    freeTree(top);

释放函数的定义

// 释放节点并设置节点为空
void freeTree(Node *&node) {
    if (node != nullptr) {
        freeTree(node->left);
        freeTree(node->right);
        delete node;
        node = nullptr;
    }
}

哈夫曼树到这里就完了,可以再往下写编码和解码的相关函数,不过解码可能会有点难度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值