贪心法——Huffman树
问题:
给定字符集C={c1,c2,…cn}以及每个字符频率f(ci),求关于C的一个最优前缀编码(总码长最小)。
分析:
每次选择最小频率的两个子树合并为新的子树,进行n-1次操作即可。
证明略。
代码:
#include<bits/stdc++.h>
using namespace std;
struct node{
node* leftChild;
node* rightChild;
char chara;
float freq;
node(float _freq,char _chara){
freq = _freq;
chara = _chara;
leftChild = NULL;//
rightChild = NULL;//注意野指针问题
}
};
struct cmp{
bool operator()(node* n1, node* n2)
{
return n1->freq > n2->freq;
}
};
// 建立Huffman树,贪心的选择
// 输入:待编码字符集c[],出现概率f[],字符数量n
// 输出:创建好的Huffman树的根节点指针
node* Huffman(char c[],float f[],int n){
// 创建优先队列
priority_queue<node*,vector<node*>,cmp> pq;
// 将每个字符作为叶节点放进优先队列
for(int i = 0;i < n;i++){
node* tmp = new node(f[i],c[i]);
pq.push(tmp);
}
// 进行n-1次操作
while(--n){// n--与--n
// 取出f值最大的两个子树,分别作为左子树和右子树合并成一棵新的树
node* tmp1 = pq.top();
pq.pop();
node* tmp2 = pq.top();
pq.pop();
node* root = new node(tmp1->freq+tmp2->freq,'*');
root->leftChild = tmp1;
root->rightChild = tmp2;
pq.push(root);
}
return pq.top();
}
// 查找某个字符的编码
// 输入:Huffman树根节点root,待查找字符c,结果记录栈res(在递归中记录中间结果很头痛,即使用全局变量或者函数加一个参数也稍显麻烦)
void showCode(node* root,char c,stack<int> res){
if(root->chara == c){
stack<int> res1;
cout<<c<<": ";
while(!res.empty()){
res1.push(res.top());
res.pop();
}
while (!res1.empty()) {
cout<<res1.top()<<" ";
res1.pop();
}
return;
}
if(root->leftChild==NULL && root->rightChild==NULL)
return;
res.push(0);
showCode(root->leftChild,c,res);
res.pop();
res.push(1);
showCode(root->rightChild,c,res);
res.pop();
}
int main(){
int n = 5;
char c[5] = {'b','c','d','e','f'};
float f[5] = {13,12,16,9,5};
stack<int> res;
showCode(Huffman(c, f, n), 'f', res);
}
复杂度:
n-1次操作,每次向堆中新插入元素,需要O(logn)复杂度,因此共O(nlogn)
P.S. CSDN的markdown语法中c++竟然是cpp,tmd,写了快一年才发现,离谱