哈夫曼编码是广泛用于数据文件压缩的十分有效的编码方法。其压缩率通常在20%~90%之间。哈夫曼编码算法使用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。
给出现频率高的字符较短的编码,出现频率较低的字符以较长的编码,可以大大缩短总码长。
1.前缀码
对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其他字符代码的前缀。这种编码称为前缀码。
编码的前缀性质可以使译码方法非常简单。
2.构造哈夫曼编码
哈夫曼提出构造最优前缀码的贪心算法,由此产生的编码方案称为哈夫曼算法。
哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。
算法以|C|个叶结点开始,执行|C|-1次的“合并”运算后产生最终所要求的树T。
3.算法步骤:(以二叉树为例)
①根据给定的 n 个权值 {w1, w2, …, wn},构造 n 棵二叉树的集合F = {T1, T2, … , Tn},其中每棵二叉树中均只含一个带权值为 wi的根结点, 其左、右子树为空树;
②在 F 中选取其根结点的权值为最小的两棵二叉树,分别作为左、右子树构造一棵新的二叉树,并置这棵新的二叉树根结点的权值为其左、右子树根结点的权值之 和;
③从F中删去这两棵树,同时加入刚生成的新树;
④重复 (2) 和 (3) 两步,直至 F 中只含一棵树为止。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 30 //待编码字符的个数,即树中叶结点的最大个数
#define M 2*N-1//树中总的结点数目
typedef struct //树中结点的结构
{
int weight;
int parent;
int LChild;
int RChild;
}HTNode,HuffmanTree[M+1];//0号单元不使用
typedef char* Huffmancode[N+1];
void select(HuffmanTree HT, int g, int *s1, int *s2);
//构造Huffman树ht
void CrtHuffmanTree(HuffmanTree ht,int w[],int n)
{
int i,s1,s2,m;
m=2*n-1;
if(n<1)
return ;
//初始化前 n 个元素成为根节点
for(i=1;i<=n;i++){
ht[i].LChild=0;
ht[i].parent=0;
ht[i].RChild=0;
ht[i].weight=w[i];
}
//初始化后n-1个为空元素
for(i=n+1;i<=m;i++)
{
ht[i].LChild=0;
ht[i].parent=0;
ht[i].RChild=0;
ht[i].weight=0;
}
//从第n+1个元素开始构造新结点
for(i=n+1;i<=m;i++){
select(ht,i-1,&s1,&s2);
(ht+i)->weight=ht[s1].weight+ht[s2].weight;
ht[s1].parent=i;
ht[s2].parent=i;
ht[i].LChild=s1;
ht[i].RChild=s2;
}
}
void select(HuffmanTree HT, int g, int *s1, int *s2)
{
int j, k, m, n;
for(k=1; k<=g; k++) //找到一个parent为0的子树
{
if(HT[k].parent==0)
{
*s2=k;
break;
}
}
for(j=1; j<=g; j++)
{
if((HT[j].weight<=HT[k].weight)&&(HT[j].parent==0)) //找到一个parent为0权值最小的子树
*s2=j;
}
for(m=1; m<=g; m++)
{
if((HT[m].parent==0)&&(m!=*s2))
{
*s1=m;
break;
}
}
for(n=1; n<=g; n++)
{
if((HT[n].weight<HT[m].weight)&&(HT[n].parent==0)&&(n!=*s2))
*s1=n;
}
}
void CrtHuffmanCode(HuffmanTree ht,Huffmancode hc,int n)
{
char *cd;
int c;
int i;
int p;
int start;
cd=(char *)malloc((n+1)*sizeof(char));
cd[n]='\0';
for (i = 1; i <= n; i ++)
{
start=n;
c= i; //c为当前节点,p为其双亲
p=ht[i].parent;
while (p!=0)
{
--start;
if (ht[p].LChild==c) cd[start]='0';
else cd[start]='1';
c=p;
p=ht[p].parent;
}
hc[i]= (char *)malloc((n-start+1)*sizeof(char));
strcpy(hc[i],cd+start);
}
free(cd);
}
void CharSetHuffmanDecoding(HuffmanTree T, char* cd, int n)
{
int p = 2*n-1; //从根结点开始
int i=0;
//当要解码的字符串没有结束时
while(cd[i]!='\0')
{
//当还没有到达哈夫曼树的叶子并且要解码的字符串没有结束时
while((T[p].LChild!=0 && T[p].RChild != 0) && cd[i] != '\0')
{
if(cd[i] == '0')
{
//如果是0,则叶子在左子树
p=T[p].LChild;
}
else
{
//如果是1,则叶子在左子树
p=T[p].RChild;
}
i++;
}
//如果到达哈夫曼树的叶子时
if(T[p].LChild == 0 && T[p].RChild == 0)
{
printf("%2d", T[p].weight);
p = 2*n-1;
}
else //如果编号为p的结点不是叶子,那么编码有错
{
printf("\n解码出错! \n");
return;
}
}
printf("\n");
}
int main(void){
HuffmanTree ht;
int i=1,temp,j;
int w[N];
printf("请输入叶子结点,输入0时结束:\n");
scanf("%d",&temp); //读入叶子结点的权值
while(temp!=0) {
w[i++]=temp;
scanf("%d",&temp);
}
CrtHuffmanTree(ht,w,i-1);
for(j=1;j<=2*(i-1)-1;j++){
printf("%d\n",ht[j].weight);
}
Huffmancode hc;
CrtHuffmanCode( ht,hc,i-1);
for(j=1;j<=i-1;j++){
printf("%d的编码为:",ht[j].weight);
puts(hc[j]);
}
char cd[100];
printf("请输入待译码串:");
scanf("%s",cd);
getchar();
puts(cd);
CharSetHuffmanDecoding(ht, cd, i-1);
return 0;
}