哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,
夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)
首先,将符号按照概率由大到小排队,编码时,从最小概率的两个符号开始,可选其中一个支 路为0,另一支路为1。这里,我们选上支路为0,下支路为1。再将已编码的两支路的概率合并,并重新排队。多次重复使用上述方法直至合并概率归一时为止。
从文件中或者输入 读入一个字符串,统计字符串中的字符种类(不包括大小写),以及每个字符出现的概率,并按照霍夫曼编程思想,构造哈夫曼树结构,将其转化成“01”的二进制字符串,并且可以读入一串01字符串,将其译为对应的哈夫曼字符。并计算其编码效率。
基本算法:
① 输入字符串 (不包含大小写),回车结束
② 根据循环遍历统计字符串中每个不同字符的概率以及总个数
③ 根据哈夫曼编码构造哈夫曼树
先将数组排序,采用优先队列求出最小的两个概率,求和得到父节点 ,加入哈夫曼树数组中,依次循环直到只剩下一个概率且为1的根节点
④ 编码,从每个叶子节点向根遍历,如果是左子树编码就为0,右子树为1.直到所有的叶子节点都编码完毕。
⑤ 根据公式求编码效率n == H(s)/l;
⑥ 译码 ,根据输入的字符串,从根节点开始想下边遍历,如果是1就向右子树,0就是左子树,直到找到一个叶子节点就退出。继续从根节点开始。
代码如下:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<queue>
#include<stack>
#include<cmath>
#include<algorithm>
#include<functional>
#define max 200
using namespace std;
struct jcode//求得概率之后的结构体
{
char c;
int x;//概率
int b;//下标
friend bool operator<(jcode a, jcode b ){//优先队列排序
return b.x<a.x;
}
};
typedef jcode jjnum[max];
typedef struct bnode{//存储哈夫曼树的结构体数组
char data;
int x;
int lchild,rchild,parent;
char code[max];//每个字符编码
int len;//编码长度
}bnode;
typedef bnode huffman[max];
//统计输入的字符及各自概率
int getnum(string ss,jjnum str)
{
int i,j;
int temp[max];
for(i = 0;i< max;i++)
{
temp[i]=0;
}
for(i = 0;i<ss.length();i++)//求概率
temp[ss[i]-'!']++;
j=0;
for(i = 0;i<max;i++)//存到结构体1
{
if(temp[i]!=0)
{
str[j].x= temp[i];
str[j].c = i+'!';
str[j].b = j;
j++;
}
}
return j;
}
void create(priority_queue<jcode>q,huffman &t,int cnum,jjnum str)//构造哈夫曼树
{
int i,a1,a2;
for(i=0;i<2*cnum-1;i++)//初始化结点
{
t[i].lchild = t[i].parent = t[i].rchild = t[i].x = 0;
t[i].data= '1';
}
for(i=0;i<cnum;i++)//叶子节点
{
t[i].x = str[i].x;
t[i].data = str[i].c;
}
for(i=cnum;i<2*cnum-1;i++)//根据哈夫曼编码构造哈夫曼树
{
a1 = q.top().b;
t[a1].parent = i;q.pop();
a2 = q.top().b;
t[a2].parent = i;q.pop();
t[i].lchild = a1;//指向孩子
t[i].rchild = a2;
t[i].x = t[a1].x+t[a2].x;//概率小的两个相加
jcode e;
e.c = '1';e.x = t[i].x;e.b = i;
q.push(e);//和存到队列中
}
for(i=0;i<cnum;i++)//由0.1进行二元编码,1右0左
{
int x;
x=i;
t[i].len = 0;
while(t[x].parent!=0)//从叶子节点向根
{
if(t[t[x].parent].lchild == x)
{
t[i].code[t[i].len]='0';//存下来编码
t[i].len++;
}
else
{
t[i].code[t[i].len]='1';
t[i].len++;
}
x = t[x].parent;
}
}
for(i=0;i<cnum;i++)//输出每个字符编码
{
if(t[i].data!='1')
{
cout<<t[i].data<<" === ";
for(int j=t[i].len-1;j>=0;j--)
cout<<t[i].code[j];
cout<<endl;
}
}
}
//编码f
void strhttree(jjnum str,int num,huffman &t)
{
int i;
priority_queue<jcode>q2;
for(i = 0; i < num; i++)//加入优先队列
q2.push(str[i]);
create(q2,t,num,str);
}
void yima(string ss,int i,huffman &t,int num,int cnum)//译码
{
while(num>=cnum)//从根节点开始
{
if(ss[i]=='1')
{
num = t[num].rchild;
i++;
}
else
{
num = t[num].lchild;
i++;
}
}
if(i<=ss.length())//得到一个叶子节点,即译出一个字符
{
cout<<t[num].data;
yima(ss,i,t,2*cnum-2,cnum);//继续译码
}
}
int main()
{
string s;//输入字符串
huffman t;//哈夫曼树
getline(cin,s);
jcode str[max];
int cnum = getnum(s,str);//字符的种类个数
strhttree(str,cnum,t);//构造哈夫曼树
for(int k = 0;k<s.length();k++)//输出编码
for(int i=0;i<cnum;i++)
{
if(s[k]==t[i].data){
for(int j=t[i].len-1;j>=0;j--)
cout<<t[i].code[j];
}
}
cout<<endl;
double sum1 =0.0,sum2 = 0.0;
for(int i = 0;i<cnum;i++)//求编码效率
{
sum1+=((t[i].x*1.0/s.size())*(-log2(t[i].x*1.0/s.size())));
sum2+=(t[i].x*1.0/s.size())*t[i].len;
}
cout<<"n=="<<(sum1/sum2)<<endl;
string l;
getline(cin,l);//输入码
int x= 2*cnum -2;//根节点、
yima(l,0,t,x,cnum);//译码
return 0;
}