Huffman树是二叉树中的一个重要运用,在信息编码方面起着重要作用。本篇博客主要介绍Huffman树构造的过程和其WPL值的计算方法。
Huffman树是一种最优二叉树。一棵二叉树树中一个结点到另一个结点之间的分支构成了这两个结点之间的路径。结点的带权路径长度是该结点到根结点之间路径长度与结点上权值的乘积,树的带权路径长度则是所有叶子结点的带权路径长度之和,带权路径长度记为WPL,其中WPL=W1*L1+W2*L2+W3*L3+...+Wn*Ln
(W为每个叶子结点的权值,L为每个叶子结点的路径长度)
如果给出n个带有权值的叶子结点,可以以此构造出一个WPL值最小的二叉树,这个二叉树,就是所谓的Huffman树。
从上面的定义可以知道,Huffman树满足的特点是所有叶子结点权值同其路径长度乘积的总和最小,依照权值之和始终最小的原则,不难想出Huffman树的构造方法。
上面的图片说明了Huffman树的形成过程。
Huffman树的形成过程是一个叶子结点与叶子结点、叶子结点与非叶结点之间排序合并的循环过程,每一次的合并选取的是所有结点中权值最小的两个,两结点合并之后,形成一棵子树,之后子树的根结点再参与排序、合并操作…
由于每一次合并的操作,选取的都是所有结点中权值最小的两个,所以一步一步操作过程中形成的一棵棵子树也是Huffman树,当所有的叶子结点全部接入一棵树上时,Huffman树就算构造成功了。
从上面图片中的例子可以看到,那个6个结点的在Huffman树上的路径依次是4,4,3,2,2,2(升序排列之后的序列),所以此Huffman树的WPL=4*1+4*1+3*2+2*4+2*6+2*8=40
上面的图片中已经给出了Huffman树的算法,知道算法之后,生成Huffman树的源码就很容易写出来了。
由于Huffman树的生成过程,是一个排序、合并的循环过程,所以代码里要有排序和合并这两个主要操作或函数。排序就是对当前的所有结点就权值来进行排序,合并主要是选取两个权值最小的结点,使之合并为一个非叶新结点,新结点的左孩子和右孩子,分别是参与合并操作的这两个结点,权值,就是左右孩子的权值之和。
排序函数的源码如下:
void node_sort(tNode *a_tNode, int size){
tNode temp;
for(int i=0; i<size; i++)
for(int j=0; j<size-1-i; j++)
if(a_tNode[j].weight > a_tNode[j+1].weight){
temp = a_tNode[j];
a_tNode[j] = a_tNode[j+1];
a_tNode[j+1] = temp;
}
}
合并结点函数的源码如下:
tNode merge(tNode _t1, tNode _t2){
tNode temp, *t1, *t2;
t1 = new tNode;
t2 = new tNode;
memcpy(t1, &_t1, sizeof(tNode));
memcpy(t2, &_t2, sizeof(tNode));
temp.weight = t1->weight + t2->weight;
temp.lchild = t1;
temp.rchild = t2;
return temp;
}
程序中要输入的是各个结点的权值,结点的信息,要用一个结构体数组来存储,此结构体数组也是用来排序的一大参数,每合并一个结点之后,都要减少一个结点,之后才能再次排序,在这个结构体数组中删除结点的函数源码如下:
void a_merge(tNode *a_tNode, tNode insert, int size){
a_tNode[0] = insert;
for(int i=1; i<size-1; i++)
a_tNode[i] = a_tNode[i+1];
memset(&a_tNode[size-1], 0, sizeof(tNode));
}
Huffman树构造好之后,就可以依次为参数,进行WPL值的计算了。
WPL值计算的过程,用递归实现最容易理解,其基本思想是:
一棵树的WPL值,是左子树叶子结点的WPL值与右子树叶子结点的WPL值之和;而当一个结点是叶子结点时,其自身的WPL值就是该结点的深度与其权重的乘积;当一个结点左右孩子都为空时,它就是叶子结点。
上面的分析以及确定了递归的结束条件和递归函数的参数列表,其实现源码如下:
int WPL(tNode *root, int deep, int wpl){
if(root){
if(!root->lchild && !root->rchild)
wpl += root->weight * deep;
else
wpl += WPL(root->lchild, deep+1, wpl) + WPL(root->rchild, deep+1, wpl);
return wpl;
}
return wpl;
}
这里再做近一步分析:
上述代码整合之后,如下:
程序源码:
#include
#include
using namespace std;
class tNode{
public:
int weight;
tNode *lchild, *rchild;
};
void node_sort(tNode *a_tNode, int size){
tNode temp;
for(int i=0; i<size; i++)
for(int j=0; j<size-1-i; j++)
if(a_tNode[j].weight > a_tNode[j+1].weight){
temp = a_tNode[j];
a_tNode[j] = a_tNode[j+1];
a_tNode[j+1] = temp;
}
}
tNode merge(tNode _t1, tNode _t2){
tNode temp, *t1, *t2;
t1 = new tNode;
t2 = new tNode;
memcpy(t1, &_t1, sizeof(tNode));
memcpy(t2, &_t2, sizeof(tNode));
temp.weight = t1->weight + t2->weight;
temp.lchild = t1;
temp.rchild = t2;
return temp;
}
void a_merge(tNode *a_tNode, tNode insert, int size){
a_tNode[0] = insert;
for(int i=1; i<size-1; i++)
a_tNode[i] = a_tNode[i+1];
memset(&a_tNode[size-1], 0, sizeof(tNode));
}
int WPL(tNode *root, int deep, int wpl){
if(root){
if(!root->lchild && !root->rchild)
wpl += root->weight * deep;
else
wpl += WPL(root->lchild, deep+1, wpl) + WPL(root->rchild, deep+1, wpl);
return wpl;
}
return wpl;
}
int main(int argc, char **argv){
int quantity;
tNode insert, *node;
cout << "Please input the quantity of the char:" << endl;
cin >> quantity;
node = new tNode[quantity];
memset(node, 0, sizeof(tNode)*quantity);
cout << "Please input the weight:" << endl;
for(int i=0; i<quantity; i++)
cin >> node[i].weight;
if(quantity==1)
insert = node[0];
for(int i=0; i<quantity-1; i++){
node_sort(node, quantity-i);
insert = merge(node[0], node[1]);
a_merge(node, insert, quantity-i);
}
cout << endl << "WPL == " << WPL(&insert, 0, 0) << endl;
return 0;
}
n个叶子结点形成的Huffman树是不唯一的,比如,将上面图片里画的Huffman树交换左右子树之后,其仍然是一棵Huffman树,且WPL==40。