1.基本概念
哈夫曼树又称为最优树,是一类带权路径长度最短的树。
一些概念的定义:
(1)路径:树的两个结点之间的连线称为路径。
(2)路径长度:路径上的分支数目称作路径长度。若规定根结点长度为1,则从根结点到第L层结点的路径长度为L-1。
(3)权:是对实体的某个或某个属性的数值化描述,可分为结点权和边权。如下:
如图所示,根结点权值为1,根结点的左子树结点权值为2,若两节点之间的连线有值,称为边权。
(4)结点的带权路径长度:从根结点到该结点之间的路径长度为该结点的权的乘积。
(5)树的带权路径长度:树中所有叶子结点的带权路径长度之和,通常记作WLP=
w为结点值,k为层数。
给定N个权值作为N个叶子结点,构造一颗二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。带权路径长度最短的树,权值较大的结点离根较近。
2.哈夫曼树的构造
(1).给定n个权值,这n棵二叉树构成森林F。
(2)在森林F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
(3)在森林F中删除这两棵树,同时将新得到的二叉树加入F中。
(4)重复(2)(3)步骤,直到F中只含有一棵树为止。
在构造哈夫曼树时候,首先选择权值小的,这样保证权值大的离根最近,这样计算带权路径长度为最小,这是一种典型的贪心法。
3.代码实现
1.一个结点需要包含的内容:
typedef struct
{
int lchild,rchild,parent; //结点的左右孩子以及父节点的下标
int weight; //权值
}*haffmantree,htnode;
2.查找权值最小的两个结点的思想是:从树起始位置开始,首先找到两个无父结点的结点(说明还未使用其构建成树),然后和后续无父结点的结点依次做比较,有两种情况需要考虑:
- 如果比两个结点中较小的那个还小,就保留这个结点,删除原来较大的结点;
- 如果介于两个结点权重值之间,替换原来较大的结点;
void select(haffmantree ht,int end,int *s1,int *s2) { int min1,min2; int i=1; while(ht[i].parent!=0&&i<=end) i++; min1=ht[i].weight; *s1=i; i++; while(ht[i].parent!=0&&i<=end) i++; if(ht[i].weight<min1) { min2=min1; *s2=*s1; min1=ht[i].weight; *s1=i; } else { min2=ht[i].weight; *s2=i; } for(int j=i+1;j<=end;j++) { if(ht[j].parent!=0) continue; if(ht[j].weight<min1) { min2=min1; min1=ht[j].weight; *s2=*s1; *s1=j; } else if(ht[j].weight>=min1&&ht[j].weight<min2) { min2=ht[j].weight; *s2=j; } } }
3.构建哈夫曼树
-
void creat(haffmantree *hp,int n,int *sum) { if(n<=1) return ; int m=2*n-1; //哈夫曼树总结点数量,n为叶子结点 *hp=(haffmantree)malloc(sizeof(haffmantree)*m); haffmantree h=*hp; //初始化哈夫曼树中的结点 for(int i=1;i<=m;i++) { h[i].parent=0; h[i].lchild=0; h[i].rchild=0; } for(int i=1;i<=n;i++) scanf("%d",&h[i].weight); //构建 for(int i=n+1;i<=m;i++) { int s1,s2; select(h,i-1,&s1,&s2); //找到森林中两个值最小的结点 h[s1].parent=i; h[s2].parent=i; h[i].lchild=s1; h[i].rchild=s2; h[i].weight=h[s1].weight+h[s2].weight; //*sum+=h[i].weight; //树的路径长度 } }