数据结构实验之二叉树六:哈夫曼编码
Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^
题目描述
字符的编码方式有多种,除了大家熟悉的ASCII编码,哈夫曼编码(Huffman Coding)也是一种编码方式,它是可变字长编码。该方法完全依据字符出现概率来构造出平均长度最短的编码,称之为最优编码。哈夫曼编码常被用于数据文件压缩中,其压缩率通常在20%~90%之间。你的任务是对从键盘输入的一个字符串求出它的ASCII编码长度和哈夫曼编码长度的比值。
输入
输入数据有多组,每组数据一行,表示要编码的字符串。
输出
对应字符的
ASCII
编码长度
la
,
huffman
编码长度
lh
和
la/lh
的值
(
保留一位小数
)
,数据之间以空格间隔。
示例输入
AAAAABCD THE_CAT_IN_THE_HAT
示例输出
64 13 4.9 144 51 2.8
提示
这里先讲c++中使用STL优先队列的方法模拟哈夫曼编码。
用法:
示例:将元素5,3,2,4,6依次push到优先队列中,print其输出。
1. 标准库默认使用元素类型的<操作符来确定它们之间的优先级关系。
priority_queue<int> pq;
通过<操作符可知在整数中元素大的优先级高。
故示例1中输出结果为: 6 5 4 3 2
2. 数据越小,优先级越高
priority_queue<int, vector<int>, greater<int> >pq;
其中
第二个参数为容器类型。
第三个参数为比较函数。
greater<T> | 表示T类型>运算。 |
和代码中的自定义cmp函数效果一样。
函数对象
故示例2中输出结果为:2 3 4 5 6
3. 自定义优先级,重载比较符号
重载默认的 < 符号
struct node { friend bool operator< (node n1, node n2) { return n1.priority < n2.priority; } int priority; int value; };
priority_queue<node> qn;
这时,需要为每个元素自定义一个优先级。
注:重载>号会编译出错,因为标准库默认使用元素类型的<操作符来确定它们之间的优先级关系。
而且自定义类型的<操作符与>操作符并无直接联系。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
char a[10000];
int b[1000];
struct cmp
{
bool operator()(const int &a,const int &b) //cmp重载了()运算符
{
return a>b;
}
};
int main()
{
int i,j,n,m,k,t,x1,x2;
priority_queue<int,vector<int>,cmp>que; //priority_queue<int ,vector<int>,greater<int> >que;
while(scanf("%s",a)!=EOF)
{
k=0;
memset(b,0,sizeof(b));
n=strlen(a);
m=n*8;
for(i=0;i<n;i++)
b[a[i]]++;
for(i=0;i<150;i++)
if(b[i]!=0)
que.push(b[i]);
while(!que.empty())
{
x1=que.top();
que.pop();
if(!que.empty())
{
x2=que.top();
que.pop();
k+=(x1+x2);
printf("%d ",k);
que.push(x1+x2);
}
}
printf("%d %d %.1lf\n",m,k,1.0*m/k);
}
}
其中注释吊的是另一种统计字符个数的方法,可能会稍微耗时,但是没注释掉的却多开辟了内存。
#include<cstdio>
#include<cstring>
#include<limits.h> //INT_MAX 的头文件
#define Max INT_MAX
using namespace std;
int j; // 统计不同字符个数
struct Tnode
{
char d;
int w; //权值
int l,r,p; //左右孩子、双亲节点
};
void CreatHT(Tnode ht[],char st[]) //创建哈夫曼树
{
int n=strlen(st);
j=0;
int m=2*n-1;
for(int i=0; i<m; i++) //赋初值,会多
{
ht[i].w=0;
ht[i].l=-1;
ht[i].r=-1;
ht[i].p=-1;
}
// for(int i=0; i<n; i++) //统计每个字符的个数
// {
// int flag=0;
// for(int k=0; k<j; k++)
// if(ht[k].d==st[i])
// {
// flag=1;
// ht[k].w++;
// break;
// }
// if(!flag)
// {
//
// ht[j].d=st[i];
// ht[j].w++;
// j++;
// }
// }
int a[1000];
memset(a,0,sizeof(a));
for(int i=0; i<n; i++)
a[st[i]]++;
for(int i=0; i<130; i++)
if(a[i]!=0)
{
ht[j++].w=a[i];
}
for(int i=j; i<j*2-1; i++)
{
int min1=Max;
int min2=Max;
int l=-1;
int r=-1;
for(int k=0; k<i; k++) //寻找当前数组中最小的两个节点
{
if(ht[k].p==-1) //在尚未构建的节点中查找
if(ht[k].w<min1)
{
min2=min1; //将上一节点: 权值赋值到min2,孩子节点赋值r
r=l;
min1=ht[k].w; //赋值当前节点
l=k;
}
else if(ht[k].w<min2)
{
min2=ht[k].w;
r=k;
}
}
ht[i].w=ht[r].w+ht[l].w;
ht[i].l=l;
ht[i].r=r;
ht[l].p=i;
ht[r].p=i;
}
}
int Print(Tnode ht[])
{
int sum=0; //计算权值
for(int i=j; i<j*2-1; i++)
sum+=ht[i].w;
return sum;
}
int main()
{
char st[1000];
while(~scanf("%s",st))
{
Tnode ht[2000];
int x=strlen(st);
x*=8; //每个ASCII码8个二进制数
CreatHT(ht,st);
int y=Print(ht);
printf("%d %d %.1lf\n",x,y,1.0*x/y);
}
}