五、哈夫曼树
1、哈夫曼树
哈夫曼(Huffma)树又称最优二叉树。它是n个带权叶子结点构成的所有二叉树中,带权路径长度WTL最小的二叉树。因为构造这种树的算法是最早由哈夫曼于1952年提出的,所以被称为哈夫曼树,相应的算法称为哈夫曼算法。
从树中某一结点到另一个节点所经过的分支构成这两个节点之间的路径。路径的分支数目叫做这两个节点之间的路径长度。
树的路径长度是从树根到树中每个结点之间路径长度之和。这种路径长度最短的是完全二叉树。
从根结点到某结点之间的路径长度与该结点所带的权值的乘积,称为该结点的带权路径长度,计作WkLk,其中Lk为结点K的路径长度,Wk为结点K的权值。
树的带权路径长度是树中所有带权结点的路径长度之和。
2、构造哈夫曼树
如何构造一棵带权路径长度最短的哈夫曼二叉树哈夫曼提出了如下算法:(1)根据n个权值{w1,w2,…,wn}构成n棵二叉树的森林T={T1,T2,…,Tn},其中Ti只有一个带权为 wi的根结点,且左右子树均为空。
(2)在T中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
(3)在T中删除这两棵树,同时将新得到的二叉树加入森林中。
(4)重复(2)和(3),直到森林中只有一棵树为止,这棵树即为哈夫曼树。
3、哈夫曼树的构造算法描述如下:
# include "stdio.h"
# include "stdlib.h"
# include "alloc.h"
#define m 100
struct ptree // 定义二叉树结点类型
{ int w; //定义结点权值
struct ptree * lchild; //定义左子结点指针
struct ptree * rchild; //定义右子结点指针
};
struct pforest // 定义链表结点类型
{ struct pforest * link;
struct ptree * root;
};
int WTL=0; //初始化WTL为0
main( )
{
struct ptree * hafm( );
void travel( );
struct ptree * head;
int n,i,w[m];
printf ("please input the sum of node \ n"); //提示输入结点数
scanf ("%d", &n); //输入结点数
printf ("please input weight of every node \ n"); //提示输入每个结点的权值
for (i=1;i<=n;i++)
scanf ("%d", &w[i]); //输入每个节点权值
head=hafm(w,n);
travel (head, 0);
printf ("The length of the best path is WTL=%d", WTL); //输出最佳路径权值之和
}
void travel (head, n) //为了验证harfm算法的正确性进行的遍历
struct ptree * head;
int n;
{
struct ptree * p;
p=head;
if (p!=NULL)
{
if ((p->lchild)= =NULL&& (p->rchild) = =NULL) //如果是叶子结点
{
printf ("%d", p->w);
printf ("the hops of the node is: %d \ n", n);
WTL=WTL+n* (p->w); //计算权值
}
travel (p->lchild, n+1);
travel (p->rchild, n+1);
}
}
struct ptree * hafm (w, n)
int n;
int w[m];
{
struct pforest * inforest ( );
struct pforest * p1, * p2, * f;
struct ptree * ti, * t, * t1, * t2;
int i;
f=(struct pfores * )malloc(sizeof(struct pforest));
f->link=NULL;
for (i=1;i<=n;i++) // 产生n棵只有根结点的二叉树
{
ti= (struct ptree *)malloc(sizeof(struct ptree)); //开辟新的结点空间
ti->w=w[i]; //给结点赋权值
ti->lchild=NULL;
ti->rchild=NULL;
f=inforest (f, ti);
}
while(((f->link)->link)!=NULL) // 至少有二棵二叉树
{
p1=f->link;
p2=p1->link;
f->link=p2->link; // 取出前两棵树
t1=p1->root;
t2=p2->root;
free(p1); //释放p1
free(p2); //释放p2
t=(struct ptree *)malloc (sizeof(struct ptree)); //开辟新的结点空间
t->w=t1->w+t2->w; // 权相加
t->lchild=t1;
t->rchild=t2; // 产生新二叉树
f=inforest (f, t);
}
p1=f->link;
t=p1->root;
return(t); //返回t
}
struct pforest * inforest (f, t)
struct pforest * f;
sturct ptree * t;
{
struct pforest * p, * q, * r;
struct ptree * ti;
r=(struct pforest *) malloc(sizeof(struct pforest)); //开辟新的结点空间
r->root=t;
q=f;
p=f->link; //
while (p!=NULL) // 寻找插入位置
{
ti=p->root;
if (t->w>ti->w) //如果t的权值大于ti的权值
{
q=p;
p=p->link; //p向后寻找
}
else
p=NULL; // 强迫退出循环
}
r->link=q->link;
q->link=r; //r接在q的后面
return (f); //返回f
}