(1)题目内容及要求:
哈夫曼树及其应用
设计目的:
1. 熟悉树的各种存储结构及其特点。
2. 掌握建立哈夫曼树和哈夫曼编码的方法及带权路径长度的计算。
设计内容:
欲发一封内容为AABBCAB ……(共长 100 字符,其中:A 、B 、C 、D 、E 、F分别有7 、 9 、12 、22 、23、27个)的电报报文,实现哈夫曼编码。
设计要求:
1. 分析系统需求。
2. 建立哈夫曼树。
3. 进行哈夫曼编码,并求出平均编码长度。
4. 编程实现2、3步骤。
(2)概要设计:
1.构造哈夫曼树
2.实现哈夫曼编码
(3)详细设计:
本道题目是要求计算哈夫曼编码,采用哈夫曼编码的目的在于是发送的电文尽可能的少,比如对出现较多次数的字符采用尽可能短的编码,实现哈夫曼编码首先要构造哈夫曼树,构造哈夫曼树方法:
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,哈夫曼树的构造规则为:
1. 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
2. 在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
3. 从森林中删除选取的两棵树,并将新树加入森林;
4. 重复(02)、(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树
存储哈夫曼树:哈夫曼树中没有度为1的节点,所以一课有n个节点的哈夫曼树共有2n-1个节点,所以可以存储在一个2n-1大小的一维数组中,定义一个结构体,具体表示如下:
typedef struct//树结点定义
{
int weight;//权值
int parent;//父节点
int lchild;//左孩子
int rchild;//右孩子
}HTNode,*HuffmanTree;
首先初始化叶子节点和非叶子节点,让后构造哈夫曼树,用select函数找出最小和次小的两个节点,把i命名为其父节点,权值为两个相加。
哈夫曼编码:
存储:用一个char类型的二级指针存储
typedef char **HuffmanCode;//哈弗曼编码,char型二级指针
求哈夫曼编码:
叶子节点到跟节点一次球每个字符的哈夫曼编码,把根节点的所有右分支令 为1,左分支令为0,当进行哈夫曼编码的时候应该是逆序从每个叶子节点开始谈着双亲返回到根节点,每走一步得到一个编码值0或者1,可以用一个一维数组来存储编码,定义的start是标记起始位置。
这里用了一个cd[n-1]=’0’目的是让最后一个为‘0’,表示结束。当遍历左分支的时候另一个变量统计编码的长度,最后左右分支加起来就是总的编码长度
题目中要求的哈夫曼编码平均长度应该是每一个字符的哈弗曼编码长度乘以这个字符出现的概率之和,而不是哈夫曼编码总长度除以字符的个数,一定要注意,具体的做法就是当输出HC[i]的时候,我再令一个数组d[i]=strlen(HC[i])来截取他的长度,然后avg+=d[i]*w[i];就可以得出哈夫曼编码的平均长度。
(4)源代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct//树结点定义
{
int weight;
int parent;
int lchild;
int rchild;
}HTNode,*HuffmanTree;
//哈弗曼编码,char型二级指针
typedef char **HuffmanCode;
static char N[100];//用于保存正文
typedef struct//封装最小权结点和次小权结点
{
int s1;
int s2;
}MinCode;
int d[100],count1=0,count2=0,sum=0;//统计编码长度
float avg=0;//平均编码长度
//构造哈夫曼树HT,编码存放在HC中,w为权值,n为结点个数
HuffmanCode HuffmanCoding(HuffmanTree HT,HuffmanCode HC,int *w,int n)
{
int i,s1=0,s2=0;
HuffmanTree p;
char *cd;
int f,c,start,m;
MinCode min;
m=2*n-1;//哈弗曼编码需要开辟的结点大小为2n-1
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//开辟哈夫曼树结点空间 m+1 。为了对应关系,我们第0个空间不用。
//初始化n个叶子结点,w[0] = 0,main函数已赋值
for(p=HT,i=0;i<=n;i++,p++,w++)
{
p->weight=*w;
p->parent=0;
p->lchild=0;
p->rchild=0;
}
//将n-1个非叶子结点的初始化
for(;i<=m;i++,p++)
{
p->weight=0;
p->parent=0;
p->lchild=0;
p->rchild=0;
}
//构造哈夫曼树
for(i=n+1;i<=m;i++)
{
min=Select(HT,i-1);//找出最小和次小的两个结点
s1=min.s1 ; //最小结点下标
s2=min.s2;//次小结点下标
HT[s1].parent=i;
HT[s2].parent=i;
HT[i].lchild=s1;
HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;//赋权和
}
//打印哈弗曼树
printf("哈夫曼树如下:\n");
printf("Number\tweight\tparent\tlchild\trchild\n");
for(i=1;i<=m;i++)
{
printf("%d\t%d\t%d\t%d\t%d\t\n",i,HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);
}
//从叶子结点到根节点求每个字符的哈弗曼编码
HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
cd=(char *)malloc(n*sizeof(char *));//为哈弗曼编码动态分配空间
cd[n-1]='\0';//如:3个结点编码最长为2。cd[3-1] = '\0';
for(i=1;i<=n;i++)//求叶子结点的哈弗曼编码
{
start=n-1;//从最下面的节点开始,start为开始节点···
//从最下面的1号节点开始往顶部编码(逆序存放),然后编码2号节点,3号......
for(c=i,f=HT[i].parent; f!=0; c=f,f=HT[f].parent)
{
if(HT[f].lchild==c)
{
cd[--start]='0'; //定义左子树为0,右子树为1
count1++;
}
else
{
cd[--start]='1';
count2++;
}
}
HC[i]=(char *)malloc((n-start)*sizeof(char *));//为第i个字符分配编码空间
strcpy(HC[i],&cd[start]); //将当前求出结点的哈弗曼编码复制到HC
}
free(cd);
return HC;
}
MinCode Select(HuffmanTree HT,int n)
{
int min,secmin;
int temp = 0;
int i,s1,s2,tempi = 0;
MinCode code ;
s1=1;
s2=1;
min = 10000;//足够大
for(i=1;i<=n;i++) //找出权值weight最小的结点,下标保存在s1中
{
if(HT[i].weight<min && HT[i].parent==0)
{
min=HT[i].weight;
s1=i;
}
}
secmin = 10000;//足够大
for(i=1;i<=n;i++) //找出权值weight次小的结点,下标保存在s2中
{
if((HT[i].weight<secmin) && (i!=s1) && HT[i].parent==0)
{
secmin=HT[i].weight;
s2=i;
}
}
code.s1=s1; //放进封装中
code.s2=s2;
return code;
}
void main()
{
HuffmanTree HT=NULL;
HuffmanCode HC=NULL;
int *w=NULL;
int i,n;
char tran[100];
printf("输入节点:");
gets(N);
int len=strlen(N);
fflush(stdin);
n = strlen(N);
w=(int *)malloc((n+1)*sizeof(int *));//开辟n+1个长度的int指针空间
w[0]=0;
printf("输入节点的权值:\n");
//输入结点权值
for(i=1;i<=n;i++)
{
printf("w[%d]=",i);
scanf("%d",&w[i]);
}
fflush(stdin);
//构造哈夫曼树HT,编码存放在HC中,w为权值,n为结点个数
HC=HuffmanCoding(HT,HC,w,n);
//输出哈弗曼编码
printf("哈夫曼编码为:\n");
printf("Number\tWeight\tCode\n");
for(i=1;i<=n;i++)
{
printf("%c\t%d\t%s\n",N[i-1],w[i],HC[i]);
d[i]=strlen(HC[i]);
}
fflush(stdin);
sum=count1+count2;
printf("节点长度为:%d",len);
printf("\n编码总长度为:%d",sum);
for(i=1;i<=n;i++)
{
avg+=d[i]*w[i]*0.01;
}
printf("\n平均编码长度为:%f",avg);
return;
}
(3)运行结果及分析:
(6)收获及体会:
1.通过本次实训,让我对哈夫曼树以及哈夫曼树的应用有了一个更深刻的了解,也遇到了许多的问题,比如在存储哈夫曼编码的时候要用一个char类型的二级指针,还有在编码的时候那个结束符的问题,应该是令最后一个字符的后一个为‘0’,就是c[n-1]=’0’,当进行哈夫曼编码的时候应该是从每个叶子节点开始谈着双亲返回到根节点,每走一步得到一个编码值0或者1,可以用一个一维数组开存储编码,还有就是select函数的编写调试了好多遍,定义的s1和s2分别存放最小和次小的值,如果这个节点比最小值小而且没有父节点,即为最小节点,次小值节点的查找满足三个条件,首先不能有父节点,并且不能等于找到的最小节点的序号,找到之后返回最小值节点和次小值节点。题目中定义了许多变量,有时候就会 记混,所以要搞明白每一个变量的含义,做题目的感受是一定要注重细节,很可能一个小地方的错误就导致整个代码运行不出来,比如定义哈夫曼树的时候多加了一个*号,等等,避免犯一些不必要的错误,所以一定要细心,还有当时求平均编码长度的时候自己误认为是编码总长度除以字符的个数,查询了相关资料之后明白应该是每一个字符的哈夫曼编码长度乘以这个字符出现的概率之和。