转载自:http://blog.163.com/zhoumhan_0351/blog/static/3995422720098275836215/
一、基本概念
1、赫夫曼(Huffman)树又称最优二叉树或最优搜索树,是一种带权路径长度最短的二叉树。在许多应用中,常常赋给树中结点一个有某种意义的实数,称此实数为该结点的权。从树根结点到该结点之间的路径长度与该结点上权的乘积称为结点的带权路径长度(WPL),树中所有叶子结点的带权路径长度之和称为该树的带权路径长度,通常记为:
2、两结点间的路径:从一结点到另一结点所经过的结点序列;路径长度:从根结点到相应结点路径上的分支数目;树的路径长度:从根到每一结点的路径长度之和。
3、深度为k,结点数为n的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从1到n的结点一一对应时,称为完全二叉树。在结点数目相同的二叉树中完全二叉树是路径长度最短的二叉树。
4、WPL最小的二叉树是最优二叉树(Huffman 树)。
5、赫夫曼(Huffman)树的特征
① 当叶子上的权值均相同时,完全二叉树一定是最优二叉树。否则完全二叉树不一定是最优二叉树。
② 在最优二叉树中,权值越大的叶子离根越近。
③ 最优二叉树的形态不唯一,但WPL最小。
如上图中,只有(d)才是赫夫曼树。其中,圆围中的数值代表权值。
二、算法思想
(1) 以权值分别为W1,W2...Wn的n各结点,构成n棵二叉树T1,T2,...Tn并组成森林F={T1,T2,...Tn},其中每棵二叉树 Ti仅有一个权值为 Wi的根结点;
(2) 在F中选取两棵根结点权值最小的树作为左右子树构造一棵新二叉树,并且置新二叉树根结点权值为左右子树上根结点的权值之和(根结点的权值=左右孩子权值之和,叶结点的权值= Wi)
(3) 从F中删除这两棵二叉树,同时将新二叉树加入到F中;
(4) 重复(2)、(3)直到F中只含一棵二叉树为止,这棵二叉树就是Huffman树。
三、C语言描述
(1)我们用如下结构来存储赫夫曼树:
用大小为2n-1的一维数组来存储哈夫曼树中的结点,其存储结构为:
#define n 100 //叶结点数目 #define m 2*n-1 //树中结点总数 typedef struct { float weight; //权值,设权值均大于零 int lchild,rchild,parent; //左右孩子及双亲指针 } HTNode; typedef HTNode HuffmanTree[m]; //哈夫曼树是一维数组
因为C语言数组的下界为0,用-1表示空指针。树中结点的lchild、rchild和parent不等于-1时,分别表示该结点的左、右孩子和双亲结点在数组中的下标。
设置parent域有两个作用:一是使查找某结点的双亲变得简单;二是可通过判定parent的值是否为-1来区分根与非根结点。
(2)哈夫曼算法的简要描述
在上述存储结构上实现的哈夫曼算法可大致描述为(设T的类型为HuffmanTree):
①初始化 将T[0…m-1]中2n-1个结点里的三个指针均置为空(即置为-1),权值置为0。
②输入 读入n个叶子的权值存于数组的前n个分量(即T[0…n-1])中。它们是初始森林中n个孤立的根结点上的权值。
③合并 对森林中的树共进行n-1次合并,所产生的新结点依次放入数组T的第i个分量中(n≤i≤m-1)。每次合并分两步:
1) 在当前森林T[0…i-1]的所有结点中,选取权值最小和次小的两个根结点T [p1]和T[p2]作为合并对象,这里0≤p1,p2≤i-1。
2) 将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树,新树的根是新结点T[i]。
具体操作:
将T[p1]和T[p2]的parent置为i;
将T[i]的lchild和rchild分别置为p1和p2;
新结点T[i]的权值置为T[p1]和T[p2]的权值之和。
合并后T[pl]和T[p2]在当前森林中已不再是根,因为它们的双亲指针均已指向了T[i],所以下一次合并时不会被选中为合并对象。
(3)赫夫曼算法的数组法构造
void CreateHuffmanTree(HuffmanTree T) { int i,p1,p2; //构造哈夫曼树,T[m-1]为其根结点 InitHuffmanTree(T); //T初始化 InputWeight(T); //输入叶子权值至T[0..n-1]的weight域 for(i=n;i<m;i++) { SelectMin(T,i-1,&p1,&p2);//共进行n-1次合并,新结点依次存于T[i]中 //在T[0…i-1]中选择两个权最小的根结点,其序号分别为p1和p2 T[p1].parent=T[p2].parent=i; T[i].1child=p1; //最小权的根结点是新结点的左孩子 T[i].rchild=p2; //次小权的根结点是新结点的右孩子 T[i].weight=T[p1].weight+T[p2].weight; }//for }//CreateHuffman