实验三 哈夫曼编码
问题描述与实验目的:
给定n个字母(或字)在文档中出现的频率序列X=<x1,x2,…,xn>,求出这n个字母的Huffman编码。为方便起见,以下将频率用字母出现的次数(或称权值)w1,w2,…,wn代替。
输入
输入文件中的开始行上有一个整数T,(0<T<=20),表示有T组测试数据。
接下来是T行测试数据的描述,每组测试数据有2行。测试数据的第1行上是一个正整数n,(n<50),表示序列的长度。第2行是n个字母出现的权值序列w1,w2,…,wn,它们均为正整数,相邻的两个整数之间用空格隔开。
输入直到文件结束。
输出
对输入中的每组有n个权值的数据,应输出n+1行:先在一行上输出“Case #”,其中“#”是测试数据的组号(从1开始);接下来输出n行,其第1行到第n行上依次输出第i个字母出现的次数和相应的Huffman编码,格式如下:
wi Huffman编码。
每组测试数据对应的输出最后结束时加一个空行,以便区分。
为保证Huffman编码的唯一性,在构造Huffman树的过程中,我们约定:
1.左儿子标记为0,右儿子标记为1;
2.左儿子的权值>=右儿子的权值;
3.相同权值w的两个字母x、y,先输入权值的字母x的Huffman编码长度不超过后输入权值的字母y的Huffman编码长度。
4.合并两个节点后新的权值应从右到左搜索、插入到相应的位置。
例如:输入权值序列8 9 3 4 1 2,其Huffman编码求解过程如下,参考图A-J:
注意,如图C中权值1、2对应的节点合并后得权值为3的新节点,它插入到权值为3的原有节点的右边。
输入样例
2
6
9 8 3 4 1 2
8
60 20 5 5 3 3 3 1
输出
Case 1
9 00
8 01
3 100
4 11
1 1011
2 1010
Case 2
60 0
20 10
5 1101
5 1110
3 11000
3 11001
3 11110
1 11111
分析:
哈夫曼编码本质是个贪心的算法。将每个字符视作一个带权结点(子树)。
每次优先选择权值最小的两个子树,将二者合并,权值相加,权值小的作为右子结点(1),权值大的作左子结点(0),形成一个新的子树。再将这棵子树放回优先队列中,重复以上的操作。
本题要输出每个字符对应的哈夫曼编码,数据n<50较小,所以不考虑用数据结构建树,用直接保存路径的方法实现。自定义一个结构体,内部记录子树的权值和所有叶子结点。
用优先队列存储所有子树,因为堆排序是一种不稳定排序,可能导致相同权值的子树顺序颠倒,所以重载比较运算符,使用两个关键字排序。
两个子树合并的时候,将左子树记录的叶子结点编码全部加上''0',右子树记录的结点编码全部加'1'。当队列中结点数为1时,算法结束。
最后根据读入的顺序输出0-1字符串即可。
运行结果:
OJ测评结果:
源代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 105;
string res[55];
struct node{
int val,id;
vector<int> dp;
bool operator < (const node & rhs) const{ //双关键字排序
if(val==rhs.val) return id < rhs.id;
return val > rhs.val;
}
}vz[maxn];
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int T,cas=1; scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=0;i<=n;++i) res[i].clear();
priority_queue<node> Q;
for(int i=1,w;i<=n;++i){
node tmp;
scanf("%d",&w);
vz[i].val = w;
tmp.val = w;
tmp.id = i;
tmp.dp.push_back(i);
Q.push(tmp); //初始将每个结点视作子树
}
int cnt = n+1;
while(!Q.empty()){
node x = Q.top(); Q.pop();
if(Q.empty()) break;
node y = Q.top(); Q.pop(); //取出权值最小的两个子树
node t;
t.val = x.val + y.val; //合并权值
t.id = cnt++;
for(int i=0,sz = x.dp.size() ;i < sz ;++i){ //左子树
int id = x.dp[i];
res[id].push_back('1');
t.dp.push_back(id); //合并叶子结点
}
for(int i=0,sz = y.dp.size();i < sz; ++i){ //右子树
int id = y.dp[i];
res[id].push_back('0');
t.dp.push_back(id);
}
Q.push(t);
}
printf("Case %d
",cas++);
for(int i=1;i<=n;++i){
reverse(res[i].begin(),res[i].end());
cout<<vz[i].val<<" "<<res[i]<<endl;
}
puts("");
}
return 0;
}