哈夫曼树:带权路径长度最小的树(最优二叉树)
构造思路:
1.根据给定的n个权值构成n颗二叉树的集合(森林),其中每颗二叉树Ti中只有一个带权为wi的根结点,左右子树均空
2.在森林中选取两颗根结点最小的树作为左右子树构造一颗新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和
3.在森林中删除这两棵树,同时将新的树加入森林中
4.重复2与3,直到森林中只剩下一棵树,这棵树就是哈夫曼树
注:每次选取权值最小的两棵树用到了堆排序,用小根堆存储数据,每次从堆顶取数据。一共合并n-1次,维护小根堆复杂度为logn,所以哈夫曼树的时间复杂度是O(nlogn)
哈夫曼编码:
根据每种字符出现的频率设置长度合适的二进制编码,频率越高的字符编码越短。且为了防止编码重合导致多译的情况,要求任一个字符的编码都不是另一个字符编码的前缀。
构造思路:运用哈夫曼树,每个叶子结点就是字符的频率,左分支表示‘0’,右分支表示‘1’。这样既符合带权路径长度最小,又满足了前缀编码的条件(字符处于叶子结点上,所以到达任意一个字符都不会完全经过其它字符,不会出现A是B的前缀的情况)
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
struct HuffmanNode {//定义哈夫曼树的节点结构
char ch;//字符
double fre;//频率
HuffmanNode* left;//左子节点
HuffmanNode* right;//右子节点
HuffmanNode(char c,double f):ch(c),fre(f),left(nullptr),right(nullptr){}
};
struct Compare {//定义比较器用于小根堆
bool operator()(HuffmanNode* a, HuffmanNode* b) {
return a->fre > b->fre;//让频率小的排在前面
}
};
HuffmanNode* Build(vector<char>& characters, vector<double>& frequencies);//哈夫曼树
void Code(HuffmanNode* root, string code, vector<string>& HuffmanCodes);//哈夫曼编码
int main() {
vector<char> characters = { 'a', 'b', 'c', 'd', 'e' };
vector<double> frequencies = { 5, 9, 12, 13, 16 };
HuffmanNode* root = Build(characters, frequencies);
vector<string> huffmanCodes(256); // Assuming ASCII characters
Code(root, "", huffmanCodes);
// 输出字符和对应的哈夫曼编码
for (int i = 0; i < 256; ++i) {
if (!huffmanCodes[i].empty()) {
cout << "'" << (char)i << "': " << huffmanCodes[i] << endl;
}
}
return 0;
}
HuffmanNode* Build(vector<char>& characters, vector<double>& frequencies) {//传入字符与频率,构建哈夫曼树
priority_queue<HuffmanNode*, Compare> H;//定义小根堆用于找出最小结点与次小结点
int n = characters.size();//一共n个结点
for (int i = 0;i < n;i++) {//构建森林
HuffmanNode* Node = new HuffmanNode(characters[i], frequencies[i]);
H.push(Node);
}
while (--n) {//n个结点要进行n-1次合并过程
HuffmanNode* leftNode = H.top();//找出最小结点
H.pop();
HuffmanNode* rightNode = H.top();//找出次小结点
H.pop();
HuffmanNode* node = new HuffmanNode('\0', leftNode->fre + rightNode->fre);//合并
node->left = leftNode;
node->right = rightNode;
H.push(node);
}
return H.top();//返回哈夫曼树的根结点
}
void Code(HuffmanNode* root, string code, vector<string>& HuffmanCodes) {//进行哈夫曼编码,存储在HuffmanCodes中
if (root == nullptr) return;
if (root->left == nullptr && root->right == nullptr) {// 如果当前节点是叶子节点,说明找到了一个字符的编码
HuffmanCodes[root->ch] = code;
}
// 向左子树递归,添加 '0' 到当前编码
Code(root->left, code + "0", HuffmanCodes);
// 向右子树递归,添加 '1' 到当前编码
Code(root->right, code + "1", HuffmanCodes);
}