用了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];
}