Huffman编码 POJ 1521

写题解之前真的想先默念三声:傻逼题傻逼题傻逼题。

这是我第一次在博客里爆粗口,因为这道题黑了我20次提交记录,满页红。


这道题很水很水,连模板题都要算不上了,为什么要写到这里呢,因为这是我第一次了解到Huffman编码。


题意:题目描述的很复杂,但是搜题意的时候发现大家基本都说的很简洁,就是让你求Huffman树的带权路径长度。然后输出原字符串占用空间(原每个字符占用8位),压缩后占用空间(带权路径长度——当直接用出现次数表示频率时),以及两个值之比。

所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。

详情可以去看:维基百科:霍夫曼编码,在这里我只写我的理解了。

-------------------------------------------------------------------------------------

Huffman编码是对数据进行无损压缩的一种编码方式,它按照每个字符的出现频率进行不同的编码。比如说英文单词中's'的出现频率很高,那么很有可能用1位来存储's',而'j'的出现频率很低,那么可能用原本长度8位或者更多来表示'j'。平均下来,是节省空间的,当然均匀分布的数据是不变的。


如果要得到Huffman编码就要构建Huffman树,这个过程并不复杂,最终态的树的叶子结点都是被编码的字符,而且是一颗二叉树。构造过程是,首先要知道每种字符的出现频率,表示为节点权值;每个节点的权值都表示这个节点以及此节点的子树的权值和,一开始的时候,就是n种字符的n棵单个节点的树,每次我们找出两棵权值(即根节点权值)最小的树合并,新建立一个父节点,被合并的两棵树分别是左右子树,直到只剩下一棵树。

那么最后剩下的一棵树就是Huffman树,我们可以以走向左子树记为0,走向右子树记为1,从根节点走到每个叶子结点,就可以得到压缩后的每种字符的编码。


找权值最小的过程可以用C++ STL里的priority_queue解决,取出最小的两个,合并,加入。

-------------------------------------------------------------------------------------

对于这道题,甚至不需要构造出来Huffman树,因为它只需要带权路径长度,我们可以仅仅在每次把合并成的新的“树”加入到堆里时把它的权值加到ans里即可。就相当于把每个非叶子节点的权值求和,最后就是带权路径长度。没有必要非把树建出来,再DFS求每个节点深度。


为什么WA了将近20次,要小心输入中可能有空格以及汉字。空格很容易料到,但是汉字就不好说了,20次很大一部分是没有注意到输入中还有汉字。

用string不能用char数组,至少我是栽在了char数组上,输入一有汉字就会挂。读入最好用getline。

另外还要注意输入中可能只有一种字符,那么本身就只有一棵树,大部分写的构建Huffman树的停止条件都是堆里只有一个元素,这种情况加个特判就可以了。

真是悲伤的经历。

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;

string s;
priority_queue <int, vector<int>, greater<int> > q;

int main()
{
	while(getline(cin, s) && s != "END"){
		
		int t = 1;
		sort(s.begin(), s.end());
		for(int i = 1; i < s.length(); i++){
			if(s[i] != s[i-1]){
				q.push(t);
				t = 1; 
			}
			else t++;
		}	q.push(t); 
	
		if(q.size() == 1) {
			printf("%d %d 8.0\n", s.length()*8, s.length());
			q.pop();
			continue;	
		}
	
		int ans = 0;
		while(q.size() > 1){
			int a = q.top(); q.pop();
			int b = q.top(); q.pop();
			q.push(a+b);
			ans += a+b;
		}	q.pop();
		printf("%d %d %.1lf\n", s.length()*8, ans, (double)s.length()*8.0/(double)ans);
	}	
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值