一、实验目的
1. 理解哈夫曼树及其应用。
2. 掌握生成哈夫曼树的算法。
二、实验原理
构造哈夫曼树就是找带全路径长度最短的树,再根据构造出来的树找出结点对应的哈夫曼编码
(1)Select()函数:从无双亲的结点中选出权值最小的一个
实现步骤:先假设一个无双亲的结点k为最小结点,接着遍历所有无双亲的结点,只要检查到某个结点的权值比当前结点k的权值更小,就把这个更小的结点设为最小结点k,所以最后得到的K就是最小权值结点
(2)main()函数:
1、输入合法权值
2、给(2*l-1)个结点初始化
3、运用Select()函数来构造哈夫曼树
4、根据构造好的哈夫曼树找到每个叶子结点对应的哈夫曼编码(从叶子结点到
根节点,每走一步找一个编码)
三、参考程序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define LEN sizeof(struct HTnode)
int i,l,n,w=0,c,start,a1,a2,f;
struct HTnode {
unsigned int weight;
unsigned int parent,lchild,rchild;
}*p,*HT;
typedef char **Huffmancode;
Huffmancode HC;
char *cd;
//选出当前权值最小的结点
select(){
int k=1,j,flag=0;//k一直记录当前最小值
while((HT+k)->parent!=0) k++;
for(j=k+1;j<=n;j++,flag=0){
if((HT+j)->parent!=0) flag=1;
if((HT+j)->weight==0) flag=1;
if(!flag) {
if((HT+j)->weight<(HT+k)->weight)
k=j;
}
return(k);
}
}
main(){
printf(“\n赫夫曼树的建立:\n”);
printf(“请输入权值(叶子)数目:”);
scanf(“%d”,&l);
while(l<1) {printf(“输入错误,请重新输入权值数目:”); scanf(“%d”,&l); }
if(l==1) printf(“\n只有一个权值,无须建立赫夫曼树!”);
else {
n=2*l-1;
HT=(struct HTnode*)malloc((n+1)*LEN);
printf(“请按对应顺序输入权值(输入一权值,键入一回车):\n”);
for(i=1,p=HT+1;i<=l;++i,++p){//1~l是叶节点,给叶节点输入权值
scanf(“%d”,&w);
while(w<=0){printf(“权值错,重新输入此权值:”); scanf(“%d”,&w);}
p->weight=w; p->parent=0;
p->lchild=0; p->rchild=0;
}
for(i=l+1;i<=n;++i,++p){//l+1~n为双亲结点初始化
p->weight=0; p->parent=0;
p->lchild=0;
}
//构造哈弗曼树
for(i=l+1;i<=n;++i){//构造双亲结点,选择合适的左右结点,构造子二叉树
//选出当前结点中权值最小的两个a1,a2,并把这两个结点的双亲指定为i
a1=select(); (HT+a1)->parent=i;
a2=select(); (HT+a2)->parent=i;
//对应的i的左右孩子设为a1,a2,且权值为两者之和
(HT+i)->lchild=a1;
(HT+i)->rchild=a2;
(HT+i)->weight=(HT+a1)->weight+(HT+a2)->weight;
}
//为哈弗曼树编码
HC=(Huffmancode)malloc((l+1)*sizeof(char *));
cd=(char *)malloc(l*sizeof(char));
*(cd+(l-1))=’\0′;
//为l个叶子结点找哈弗曼编码
for(i=1;i<=l;++i){
start=l-1;
for(c=i,f=(HT+i)->parent;f!=0;c=f,f=(HT+f)->parent){//从叶子结点开始找对应编码,直到从此叶子结点一直找到根节点为止
//左结点为0,右结点为1
if((HT+f)->lchild==c) *(cd+(–start))=’0′;
else *(cd+(–start))=’1′;
//把每次得到的一位编码组合,最后得到此结点的最终编码
*(HC+i)=(char *)malloc((l-start)*sizeof(char));
strcpy(*(HC+i),(cd+start));
}
}
printf(“\n对应的二进制赫夫曼编码为:\n”);
for(i=1;i<=l;++i)
{printf(“%s”,*(HC+i));
printf(” “);
}
}
}
总结
哈夫曼树种权值越大的结点越靠近根结点,而权值越小的结点则越远离结点。根据这一特点可以得到哈夫曼树的构造方法,构造方法如下:
(1)由给定的n个权值(W1 ,W2, W3, … ,Wn)构造n棵只有一个叶结点的二叉树,从而得到二叉树的集合F = {T1, T2,…, Tn};
(2)在F张选取根结点的权值最小和次小的两颗二叉树分别最为左右子树构造一颗新的二叉树,这棵新的二叉树的根节点的权值为左右子树结点的权值之和;
(3)在集合F中删除为左右子树的两个二叉树,并将新建立的二叉树加入到集合F中;
(4)重复(2),(3)步骤,当F中只剩下一棵二叉树,即为我们所要建立的哈夫曼树;
由哈夫曼树的构造方法可知,哈夫曼树具有以下特点:
(1)满二叉树不一定是哈夫曼树树;
(2)哈夫曼树不存在度数为1的结点;
(3)根据(2)可得,根据二叉树的性质得n个叶结点的二叉树有n-1个度数为2的结点,所以整棵二叉树共有2n-1个结点;
哈夫曼树的构造:
[cpp]
view plain
copy
- #include <stdio.h>
- #include <stdlib.h>
- #define MAXVALUE 1000 //定义最大权值;
- #define MAXLEAF 30 //叶结点的最多个数
- #define MAXNODE MAXLEAF*2-1 //哈夫曼树的结点个数
- typedef struct
- {
- int weight;
- int parent;
- int lchild;
- int rchild;
- }HNodeType;
- /*———————————–
- 函数功能:构造哈夫曼树
- 函数参数:哈夫曼树HuffNode
- 叶子结点的个数n
- ———————————–*/
- void Huffman(HNodeType HuffNode[],int n)
- {
- int m1,m2,x1,x2,i,j;
- //初始化
- for(i = 0;i < 2*n-1;i++)
- {
- HuffNode[i].weight = 0;
- HuffNode[i].parent = -1;
- HuffNode[i].lchild = -1;
- HuffNode[i].rchild = -1;
- }
- //输入结点的权重
- printf(“Input the weight of node:”);
- for(i = 0;i < n;i++)
- {
- scanf(“%d”,&HuffNode[i].weight);
- }
- //构造哈夫曼树n-1个非叶子结点
- for(i = 0;i < n-1;i++)
- {
- //m1,m2分别保存F中权值最小的权重
- m1 = m2 = MAXVALUE;
- //x1,x2保存两个权值最小的结点的下标
- x1 = x2 = 0;
- for(j = 0;j < n+i;j++)
- {
- if(HuffNode[j].weight < m1 && HuffNode[j].parent == -1)
- {
- m2 = m1;
- x2 = x1;
- m1 = HuffNode[j].weight;
- x1 = j;
- }
- else if(HuffNode[j].weight < m2 && HuffNode[j].parent == -1)
- {
- 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;
- }
- }
哈夫曼树的应用
(1)哈夫曼编码
[cpp]
view plain
copy
- #define MAXBIT 10 //哈夫曼编码的最大长度
- typedef struct
- {
- <span style=“white-space:pre”> </span> int bit[MAXBIT]; //保存哈夫曼编码
- <span style=“white-space:pre”> </span> int start; //编码存放在从start+1到MAXBIT
- }HCodeType;
- /*———————————-
- *函数功能:哈夫曼编码
- ———————————-*/
- void HuffmanCode(HNodeType HuffNode[],int n)
- {
- HCodeType HuffCode[MAXLEAF],cd;
- int i,j,c,p;
- //求每一个叶子结点的哈夫曼编码
- for(i = 0;i < n;i++)
- {
- cd.start = MAXBIT-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 < MAXBIT;j++)
- {
- HuffCode[i].bit[j] = cd.bit[j];
- }
- HuffCode[i].start = cd.start;
- }
- //输出
- for(i = 0; i < n;i++)
- {
- for(j = HuffCode[i].start+1;j < MAXBIT;j++)
- {
- printf(“%d”,HuffCode[i].bit[j]);
- }
- printf(“\n”);
- }
- }
[cpp]
view plain
copy
- /*———————————-
- 函数功能:对哈夫曼编码进行解码
- 函数参数:哈夫曼树HuffNode
- 待解码的字符串code
- 叶子结点个数n
- ———————————*/
- void decoding(HNodeType HuffNode[],char code[],int n)
- {
- char *p = code;
- //n个叶结点的哈夫曼树总共有2n-1个结点,根结点的位置为2*n-2
- int i,root = 2*n-2;
- while(*p != ‘\0’)
- {
- i = root;
- //从根部开始遍历
- while(HuffNode[i].lchild != -1 && HuffNode[i].rchild != -1)
- {
- if(*p == ‘0’)
- i = HuffNode[i].lchild;
- else
- i = HuffNode[i].rchild;
- p++;
- }
- printf(“%3d”,HuffNode[i].weight);
- }
- printf(“\n”);
- }