内容:
从键盘接收一串电文字符,输出对应的Huffman(哈夫曼)编码,同时,能翻译由Huffman编码生成的代码串,输出对应的电文字符串。设计要求:
(1)构造一棵Huffman树;
(2)实现Huffman编码,并用Huffman编码生成的代码串进行译码;
(3)程序中字符串和权值是可变的,实现程序的灵活性。
步骤:
1.算法分析:
在电报通信中,电文是以二进制代码传送的。在发送时,需要将电文中的字符转换成二进制代码串,即编码;在接收时,要将收到的二进制代码转化为对应的字符序列,即译码。由于字符集中的字符被使用的频率是非均匀的,在传送电文时,要想使电文的总长尽可能短。因此,若对某字符集进行不等长的编码设计,则要求任意一个字符的编码都不是其他字符编码的前缀,这种编码称作前缀编码。由Huffman树求得的编码是最优的缀码,也叫Huffman编码。给出字符集和各个字符的概率分布,构造Huffman树,将Huffman树中每个分支结点的左分支标为0,将根到每个叶子路径上的标号连起来,就是该叶子所代表字符的编码。
数据结构:本设计使用结构体数组存储Huffman树。
算法设计:
程序中设计了两个函数:
(1)函数HuffmanTree()用来构造Huffman树;
(2)函数HuffmanCode()用来生成Huffman编码并输出。
程序中主函数根据提示输入一些字符和字符的权值,则程序输出哈夫曼编码;若输入电文,则可以输出哈夫曼译码。
1.构造Huffman树的算法
主程序中输入不同字符,统计不同字符出现的次数作为该字符的权值,存于data[]数组中。假设有n中字符,则有n个叶子节点,构造的哈夫曼树有2n-1个结点。具体步骤如下:
(1)将n个字符(叶结点)和其他权值存储在HuffNode数组的前n个数组元素中;将2n-1个结点的双亲和左右孩子均置-1.
(2)在所有结点中,选择双亲为-1,并选择具有最小和次小权值的结点m1和m2,用x1和x2指示这两个结点在数组中的位置,将根为HuffNode[x1]和HuffNode[x2]的两棵树合并,使其成为新结点HuffNode[n+i]的左右孩子,其权值为m1+m2。
(3)重复上述过程,共进行n-1次合并就构造了一棵Huffman树,产生的n-1个结点依次放在数组HuffNode[]的n-2n-2的单元中。
在构造哈夫曼树时,可以设置一个结构数组HuffNode保存哈夫曼树中个结点的信息,根据二叉树的性质可知,具有n个叶子结点的哈夫曼树共有2n-1个结点,所以数组HuffNode的大小设置为2n-1,数组元素的构造形式如下:
weight | lchild | rchild | parent |
其中,weight域保存结点的权值,lchild和rchild域分别保存该结点的左、右孩子结点在数组HuffNode中的序号,从而建立起结点之间的关系。为了判定一个结点是否已加入到要建立的哈夫曼树中,可通过parent域的值来确定。初始时parent的值为-1,当结点是否已加入到树中时,该结点parent的值为其双亲结点在数组HuffNode中的序号,就不会是-1了。
构造哈夫曼树时,首先将由n个字符形成的n个叶结点存放到数组HuffNode的前n个分量中,然后根据前面介绍的哈夫曼方法的基本思想,不断将两个小子树合并为一个较大的子树,每次构成的新子树的根节点顺序放到HuffNode数组中的后面n-1个分量。
构造算法如下:
#define MAXVALUE 10000
#define MAXLEAF 30
#define MAXNODE MAXLEAF*2-1
typedef struct node{
char letter;
int weight; //结点的权值
int parent; //结点的双亲
int lchild; //结点的左孩子
int rchild; //结点的右孩子
}HNodeType;
//哈夫曼树的构造算法
void HuffmanTree(HNodeType HuffNode[],int n,Message a[])
{
int i,j,m1,m2,x1,x2,temp1;char temp2;
for(i=0;i<2*n-1;i++) //HuffNode[]初始化
{
HuffNode[i].letter=NULL;
HuffNode[i].weight=0;
HuffNode[i].parent=-1;
HuffNode[i].lchild=-1;
HuffNode[i].rchild=-1;
}
for(i=0;i<n-1;i++)
for(j=i+1;j<n-1;j++)
if(a[j].num>a[i].num)
{
temp1=a[i].num;a[i].num=a[j].num;a[j].num=temp1;
temp2=a[i].s;a[i].s=a[j].s;a[j].s=temp2;
}
for(i=0;i<n;i++)
{
HuffNode[i].weight=a[i].num;
HuffNode[i].letter=a[i].s;
}
for(i=0;i<n-1;i++) //构造哈夫曼树
{
m1=m2=MAXVALUE;
x1=x2=0;
for(j=0;j<n+i;j++) //找出的两棵权值最小的子树
{
if(HuffNode[j].parent==-1&&HuffNode[j].weight<m1)
{
m2=m1;x2=x1;
m1=HuffNode[j].weight; x1=j;
}
else if(HuffNode[j].parent==-1&&HuffNode[j].weight<m2)
{
m2=HuffNode[j].weight;
x2=j;
}
}
//将找出的两棵子树合并为一棵子树
HuffNode[x1].parent=n+i;HuffNode[x2].parent=n+i;
HuffNode[n+i].weight=HuffNode[x1].weight+HuffNode[x2].weight;
HuffNode[n+i].lchild=x1;HuffNode[n+i].rchild=x2;
}
}
2.Huffman编码和译码的算法
求哈夫曼编码,实质上就是在已建立的哈夫曼树中,从叶结点开始,沿双亲的双亲链域回退到根节点,每回退一步,就走过了哈夫曼树的一个分支,从而得到一位哈夫曼码值,由于一个字符的哈夫曼编码是从根节点到相应叶结点所经过的路径上各分支所组成的0、1序列,因此先得到的分支码为所求编码的低位码,后得到的分支码为所求编码的高位码。我们可以设置一结构数组HuffCode用来存放各字符的哈夫曼编码信息,数组元素的结构如下:
bit | start |
其中,分量bit为一维数组,用来保存字符的哈夫曼编码,start表示该编码在数组bit中的开始位置。所以,对于第i个字符,它的哈夫曼编码存放在HuffCode[i].bit中的从HuffCode[i].start到n的分量上。
(1)从Huffman树的叶子结点HuffNode[i](0<=i<n)出发,通过HuffNode[c].parent找到双亲,通过lchild和rchild域可知HuffNode[c]是左分支还是右分支,若是左分支则bit[n-1-i]=0;否则bit[n-1-i]=1.
(2)将HuffNode[c]作为出发点,重复上述过程,直到找到树根位置,即进行了Huffman编码
(3)译码时首先输入二进制代码串,放在数组code中,以回车结束输入。
(4)将代码与编码进行比较,如果为0,则转向左子树;如果为1,则转向右子树,直到叶结点结束。输出叶子结点的数据域,即所对应的字符。
哈夫曼编码的实例如下:
#define MAXBIT 50
typedef struct{
char letter;
int bit[MAXBIT];
int start;
}HCodeType;
//生成哈夫曼编码
void HuffmanCode(int n,Message a[])
{
HNodeType HuffNode[MAXNODE];
HCodeType HuffCode[MAXLEAF],cd;
int i,j,c,p;
char code[30],*m;
HuffmanTree(HuffNode,n,a); //建立哈夫曼树
for(i=0;i<n;i++)
{
cd.start=n-1;
c=i;
p=HuffNode[c].parent;
while(p!=-1) //由叶结点向上直到树根
{
if(HuffNode[p].lchild==c)
cd.bit[cd.start]=0;
else
cd.bit[cd.start]=1;
cd.start--;
c=p;
p=HuffNode[c].parent;
}
for(j=cd.start+1;j<n;j++) //保存求出的每个结点的哈夫曼编码和编码的起始位
HuffCode[i].bit[j]=cd.bit[j];
HuffCode[i].start=cd.start;
}
printf("输出每个叶子的哈夫曼编码:\n");
for(i=0;i<n;i++) //输出每个叶子结点的哈夫曼编码
{
HuffCode[i].letter=HuffNode[i].letter;
printf(" %c:",HuffCode[i].letter);
for(j=HuffCode[i].start+1;j<n;j++)
printf(" %d",HuffCode[i].bit[j]);
printf("\n");
}
}
接下来举一个实例,说明一下哈夫曼编码:
下面是一串字符:abbcddd的编码结果:
根据出现的频率分别给其赋权值:a:1 b:2 c:1 d:3
根据哈夫曼树给其编码,现在输入电文:01011000111。则可输出译码:dbaddc.
2.概要设计:
使用C语言,使用的主要函数如下:
函数 | 作用 |
HuffmanTree() | 用来构造一个Huffman树 |
HuffmanCode() | 用来生成Huffman编码并输出 |
3.程序运行流程图:
4.实例运行结果:
5.总结
用哈夫曼树进行编码,不会产生二义性的问题,而且哈夫曼编码是一种能使电文代码总长最短的不等长编码,非常的巧妙。哈夫曼编码也是最基本的压缩编码的方法,对于以后学习也很重要。
6.源代码(Dev-c++ 5.11,vc++6.0编译通过):
#include<stdio.h>
#include<conio.h>
#define MAXVALUE 10000 //定义最大权值
#define MAXLEAF 30 //定义哈夫曼树中叶子节点个数
#define MAXNODE MAXLEAF*2-1
#define MAXBIT 50
#define NULL 0
typedef struct node{
char letter;
int weight; //结点的权值
int parent; //结点的双亲
int lchild; //结点的左孩子
int rchild; //结点的右孩子
}HNodeType;
typedef struct{
char letter;
int bit[MAXBIT];
int start;
}HCodeType;
typedef struct{
char s;
int num;
}Message;
//哈夫曼树的构造算法
void HuffmanTree(HNodeType HuffNode[],int n,Message a[])
{
int i,j,m1,m2,x1,x2,temp1;char temp2;
for(i=0;i<2*n-1;i++) //HuffNode[]初始化
{
HuffNode[i].letter=NULL;
HuffNode[i].weight=0;
HuffNode[i].parent=-1;
HuffNode[i].lchild=-1;
HuffNode[i].rchild=-1;
}
for(i=0;i<n-1;i++)
for(j=i+1;j<n-1;j++)
if(a[j].num>a[i].num)
{
temp1=a[i].num;a[i].num=a[j].num;a[j].num=temp1;
temp2=a[i].s;a[i].s=a[j].s;a[j].s=temp2;
}
for(i=0;i<n;i++)
{
HuffNode[i].weight=a[i].num;
HuffNode[i].letter=a[i].s;
}
for(i=0;i<n-1;i++) //构造哈夫曼树
{
m1=m2=MAXVALUE;
x1=x2=0;
for(j=0;j<n+i;j++) //找出的两棵权值最小的子树
{
if(HuffNode[j].parent==-1&&HuffNode[j].weight<m1)
{
m2=m1;x2=x1;
m1=HuffNode[j].weight; x1=j;
}
else if(HuffNode[j].parent==-1&&HuffNode[j].weight<m2)
{
m2=HuffNode[j].weight;
x2=j;
}
}
//将找出的两棵子树合并为一棵子树
HuffNode[x1].parent=n+i;HuffNode[x2].parent=n+i;
HuffNode[n+i].weight=HuffNode[x1].weight+HuffNode[x2].weight;
HuffNode[n+i].lchild=x1;HuffNode[n+i].rchild=x2;
}
}
//生成哈夫曼编码
void HuffmanCode(int n,Message a[])
{
HNodeType HuffNode[MAXNODE];
HCodeType HuffCode[MAXLEAF],cd;
int i,j,c,p;
char code[30],*m;
HuffmanTree(HuffNode,n,a); //建立哈夫曼树
for(i=0;i<n;i++)
{
cd.start=n-1;
c=i;
p=HuffNode[c].parent;
while(p!=-1) //由叶结点向上直到树根
{
if(HuffNode[p].lchild==c)
cd.bit[cd.start]=0;
else
cd.bit[cd.start]=1;
cd.start--;
c=p;
p=HuffNode[c].parent;
}
for(j=cd.start+1;j<n;j++) //保存求出的每个结点的哈夫曼编码和编码的起始位
HuffCode[i].bit[j]=cd.bit[j];
HuffCode[i].start=cd.start;
}
printf(" 输出每个叶子的哈夫曼编码:\n");
for(i=0;i<n;i++) //输出每个叶子结点的哈夫曼编码
{
HuffCode[i].letter=HuffNode[i].letter;
printf(" %c:",HuffCode[i].letter);
for(j=HuffCode[i].start+1;j<n;j++)
printf(" %d",HuffCode[i].bit[j]);
printf("\n");
}
printf(" 请输入电文(1/0):\n");
for(i=0;i<30;i++)code[i]=NULL;
scanf(" %s",&code); m=code;
c=2*n-2;
printf(" 输出哈夫曼译码:\n");
while(*m!=NULL)
{
if(*m=='0')
{
c=i=HuffNode[c].lchild;
if(HuffNode[c].lchild==-1&&HuffNode[c].rchild==-1)
{
printf("%c",HuffNode[i].letter);
c=2*n-2;
}
}
if(*m=='1')
{
c=i=HuffNode[c].rchild;
if(HuffNode[c].lchild==-1&&HuffNode[c].rchild==-1)
{ printf("%c",HuffNode[i].letter);
c=2*n-2;
}
}
m++;
}
printf("\n");
}
void main()
{
Message data[30];
char s[100],*p;
int i,count=0;
printf("\n 请输入一些字符:");
scanf("%s",&s);
for(i=0;i<30;i++)
{
data[i].s=NULL;
data[i].num=0;
}
p=s;
while(*p)
{
for(i=0;i<=count+1;i++)
{
if(data[i].s==NULL)
{
data[i].s=*p;data[i].num++;count++;break;
}
else if(data[i].s==*p)
{
data[i].num++;break;
}
}
p++;
}
printf("\n");
printf(" 不同的字符数:%d\n",count);
for(i=0;i<count;i++)
{ printf(" %c ",data[i].s);
printf(" 权值:%d",data[i].num);
printf("\n");
}
HuffmanCode(count,data);
getch();
}