给定一个字符串,求哈夫曼编码的最短长度(注释详尽)

用了multimap和priority queue两种方式来构建哈夫曼树。整个代码用chatgpt加上了极为详尽的注释。

#include <iostream>
#include <map>
#include <unordered_map>
#include <queue>
#include <cmath> // 用于计算对数

using namespace std;

// 枚举类型,表示构建哈夫曼树的方式
enum class BuildWay {
    byMultimap,       // 使用 multimap 构建
    byPriorityQueue   // 使用 priority_queue 构建
};

// 哈夫曼树节点结构体
struct TreeNode {
    int num;              // 节点的频率值
    string str;           // 如果是叶子节点,则表示对应的字符
    struct TreeNode* left;   // 左子节点指针
    struct TreeNode* right;  // 右子节点指针

    // 构造函数
    TreeNode(const string& s, const int& n) :str(s), num(n), left(nullptr), right(nullptr) {};
    TreeNode(const string& s, const int& n, TreeNode* leftChild, TreeNode* rightChild) : str(s), num(n), left(leftChild), right(rightChild) {};
};

// 比较器结构体,用于定义优先队列中节点的比较规则
struct cmp {
    // 重载函数调用运算符,确定节点的优先级顺序
    bool operator () (TreeNode* node1, TreeNode* node2) {
        return node1->num > node2->num;
    }
};

// 构建哈夫曼树的函数
TreeNode* BuildHaffmanTree(const unordered_map<string, int>& umapStrNum, const BuildWay& buildWay) {
    if (buildWay == BuildWay::byMultimap) {
        // 使用multimap构建哈夫曼树
        multimap<int, TreeNode*> mmapNumNode;
        // 将字符频率映射插入multimap中
        for (const auto& m : umapStrNum) mmapNumNode.insert({ m.second, new TreeNode(m.first, m.second) });

        // 循环迭代直到multimap中只剩一个元素(根节点)
        multimap<int, TreeNode*>::iterator it;
        for (it = mmapNumNode.begin(); it != mmapNumNode.end(); it++) {
            auto it2 = it;
            if (++it2 == mmapNumNode.end()) break;

            // 创建新节点并连接左右子节点
            string str = it->second->str + it2->second->str;
            int num = it->first + it2->first;
            TreeNode* newNode = new TreeNode(str, num, it->second, it2->second);
            mmapNumNode.insert({ num, newNode });

            it++;
        }
        cout << "使用了multimap来构建哈夫曼树" << endl;
        return it->second;
    }
    else if (buildWay == BuildWay::byPriorityQueue) {
        // 使用优先队列构建哈夫曼树
        priority_queue<TreeNode*, vector<TreeNode*>, cmp> priQue;
        // 将字符频率映射插入优先队列中
        for (const auto& u : umapStrNum) priQue.push(new TreeNode(u.first, u.second));

        // 循环直到队列中只剩一个节点(根节点)
        while (priQue.size() > 1) {
            TreeNode* node1 = priQue.top();
            priQue.pop();
            TreeNode* node2 = priQue.top();
            priQue.pop();

            // 创建新节点并连接左右子节点
            string str = node1->str + node2->str;
            int num = node1->num + node2->num;
            TreeNode* node3 = new TreeNode(str, num, node1, node2);

            priQue.push(node3);
        }
        cout << "使用了priority queue来构建哈夫曼树" << endl;
        return priQue.top();
    }
}

// 随机选择构建方式的函数
TreeNode* BuildHaffmanTree(const unordered_map<string, int>& umapStrNum) {
    srand(time(0));
    // 随机生成 0 或 1
    int p = rand() % 2;
    // 如果 p 是 0,则使用 multimap 构建哈夫曼树,否则使用 priority_queue 构建
    if (p == 0) return BuildHaffmanTree(umapStrNum, BuildWay::byMultimap);
    else return BuildHaffmanTree(umapStrNum, BuildWay::byPriorityQueue);
}

// 计算哈夫曼树的编码长度(递归)
int GetAllLength(TreeNode* node, const int& depth) {
    if (!node) return 0;
    // 如果是叶子节点,返回编码长度
    if (!node->left && !node->right) return depth * node->num;
    // 否则递归计算左右子树的编码长度并相加
    return GetAllLength(node->left, depth + 1) + GetAllLength(node->right, depth + 1);
}

// 前序遍历打印哈夫曼树
void PrintPreOrder(TreeNode* node) {
    if (!node) return;
    // 打印节点的频率值
    printf("%d", node->num);
    // 如果是叶子节点,打印对应的字符
    if (!node->left && !node->right) cout << "(" << node->str << ")";
    printf("  ");
    // 递归打印左子树
    PrintPreOrder(node->left);
    // 递归打印右子树
    PrintPreOrder(node->right);
}

// 中序遍历打印哈夫曼树
void PrintInOrder(TreeNode* node) {
    if (!node) return;
    // 递归打印左子树
    PrintInOrder(node->left);
    // 打印节点的频率值
    printf("%d", node->num);
    // 如果是叶子节点,打印对应的字符
    if (!node->left && !node->right) cout << "(" << node->str << ")";
    printf("  ");
    // 递归打印右子树
    PrintInOrder(node->right);
}

// 打印哈夫曼树的前序遍历和中序遍历结果
void PrintTreeOrder(TreeNode* node) {
    if (!node) return;
    printf("前序遍历为 ");
    PrintPreOrder(node);
    printf("\n中序遍历为 ");
    PrintInOrder(node);
    printf("\n");
}

int main() {
    // 循环执行多次压缩操作
    for (int i = 0; i < 3; i++) {
        string inputString;
        cin >> inputString;

        unordered_map<string, int> umapStrNum;
        // 统计输入字符串中各个字符的频率
        for (int i = 0; i < inputString.size(); i++) umapStrNum[inputString.substr(i, 1)]++;

        int compressedLength = 0;
        // 如果只有一个字符,则直接返回其频率值作为压缩后的长度
        if (umapStrNum.size() == 1) {
            compressedLength = umapStrNum.begin()->second;
        }
        else {
            // 构建哈夫曼树
            TreeNode* root = BuildHaffmanTree(umapStrNum);
            // 计算哈夫曼编码的压缩长度
            compressedLength = GetAllLength(root, 0);
            // 打印哈夫曼树的前序遍历和中序遍历结果
            PrintTreeOrder(root);
            // 释放树的内存空间,避免内存泄漏
            delete root;
        }

        int originalLength = 0;
        // 计算输入字符串的原始长度
        for (const auto& u : umapStrNum) originalLength += u.second;
        // 计算等长编码后的长度
        int isometricCodeLength = log2(umapStrNum.size());
        if (isometricCodeLength == 0 || originalLength % (int)(pow(2, isometricCodeLength)) != 0) isometricCodeLength++;
        isometricCodeLength *= originalLength;
        // 计算压缩率
        float compressibility = (1.000 - static_cast<float>(compressedLength) / isometricCodeLength) * 100;
        // 打印相关信息
        printf("原长度为%d\n", originalLength);
        printf("等长编码后长度为%d\n", isometricCodeLength);
        printf("经哈夫曼编码压缩后长度为%d\n", compressedLength);
        printf("压缩率为%.1f", compressibility);
        cout << "%" << endl << endl;
    }
    return 0;
}

对于multimap那一段代码,我原本的代码如下。multimap用的是num到string的映射,然后加了个string和TreeNode*的哈希表。感觉好蠢的......

    if (buildWay == BuildWay::byMultimap) {
        // 使用 multimap 构建哈夫曼树
        multimap<int, string> mapNumStr;
        // 将字符频率映射插入 multimap 中,按频率值从小到大排序
        for (const auto& m : umapStrNum) mapNumStr.insert({ m.second, m.first });

        // 字符串到节点指针的映射
        unordered_map<string, TreeNode*> umapStrNode;
        multimap<int, string>::iterator it;
        // 迭代 multimap
        for (it = mapNumStr.begin(); it != mapNumStr.end(); it++) {
            auto it2 = it;
            if (++it2 == mapNumStr.end()) break;

            // 创建新节点并连接左右子节点
            if (!umapStrNode[it->second]) umapStrNode[it->second] = new TreeNode(it->second, it->first);
            if (!umapStrNode[it2->second]) umapStrNode[it2->second] = new TreeNode(it2->second, it2->first);

            string str = it->second + it2->second;
            int num = it->first + it2->first;
            umapStrNode[str] = new TreeNode(str, num, umapStrNode[it->second], umapStrNode[it2->second]);
            mapNumStr.insert({ num, str });

            it++;
        }
        cout << "使用了multimap来构建哈夫曼树" << endl;
        return umapStrNode[it->second];
    }
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值