实验三 哈夫曼编码
问题描述与实验目的:
给定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\n",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; }
来源:https://www.cnblogs.com/xiuwenli/p/9806340.html