二叉树(五):哈夫曼编码
2012-06-06 15:13:24一、概念
树的路径长度:树的路径长度是从树根到树中每一结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。**
** 结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数。
结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。
树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和,通常记为:
其中:
n表示叶子结点的数目
wi和li分别表示叶结点ki的权值和根到结点ki之间的路径长度。
树的带权路径长度亦称为树的代价。
哈夫曼树或最优二叉树:在权为wl,w2,…,wn的n个叶子所构成的所有二叉树中,带权路径长度最小(即代价最小)的二叉树称为哈夫曼树或最优二叉树。
【例】给定4个叶子结点a,b,c和d,分别带权7,5,2和4。构造如下图所示的三棵二叉树(还有许多棵),它们的带权路径长度分别为:
(a)WPL=72+52+22+42=36
(b)WPL=7*3+5*3+2*1+4*2=46
(c)WPL=7*1+5*2+2*3+4*3=35
其中(c)树的WPL最小,可以验证,它就是哈夫曼树。
注意:
① 叶子上的权值均相同时,完全二叉树一定是最优二叉树,否则完全二叉树不一定是最优二叉树。
② 最优二叉树中,权越大的叶子离根越近。
③ 最优二叉树的形态不唯一,WPL最小。
二、构造最优二叉树
** **哈夫曼算法经常用来解决编码方面的问题,运用这种方式的编码我们称做哈夫曼编码,它是一种变长编码,其优点是可以根据字符出现的不同频率来节省空间。假设给定8个叶子结点Z、K、F、C、U、D、L、E,分别带权2、7、24、32、37、42、42、120,我们来看看这组数的哈夫曼编码过程。
最终的哈夫曼树,我们加入编码后的效果如下图:
前缀特性:一组编码中,其他任何字符的编码都不是某个字符编码的前缀。
DEED的编码: 10100101
1011001110111101的解码: DUCK
每个字符的平均编码位数:Σ(ci*fi) / fT= 785 / 306 = 2.56536
三、哈夫曼算法
哈夫曼首先给出了对于给定的叶子数目及其权值构造最优二叉树的方法,故称其为哈夫曼算法。其基本思想是:
(1)根据给定的n个权值wl,w2,…,wn构成n棵二叉树的森林F={T1,T2,…,Tn},其中每棵二叉树Ti中都只有一个权值为wi的根结点,其左右子树均空。
(2)在森林F中选出两棵根结点权值最小的树(当这样的树不止两棵树时,可以从中任选两棵),将这两棵树合并成一棵新树,为了保证新树仍是二叉树,需要增加一个新结点作为新树的根,并将所选的两棵树的根分别作为新根的左右孩子(谁左,谁右无关紧要),将这两个孩子的权值之和作为新树根的权值。
(3)对新的森林F重复(2),直到森林F中只剩下一棵树为止。这棵树便是哈夫曼树。
注意:
① 初始森林中的n棵二叉树,每棵树有一个孤立的结点,它们既是根,又是叶子
② n个叶子的哈夫曼树要经过n-1次合并,产生n-1个新结点。最终求得的哈夫曼树中共有2n-1个结点。
③ 哈夫曼树是严格的二叉树,没有度数为1的分支结点。
由哈夫曼树的构造思想可知,用一个数组存放原来的n个叶子结点和构造过程中临时生成的结点,数组的大小为2n-1.所以哈夫曼数中有两个成员字段:data和leafNum。结点有四个域:weight、lChild、rChild和parent。一下的哈夫曼树的结点类的代码实现:
public class HNode
{
private int weight;//结点权值
private int lChild; //左孩子结点
private int rChild; //右孩子结点
private int parent; //父结点
//结点权值属性
public int Weight
{
get
{
return weight;
}
set
{
weight = value;
}
}
//左孩子结点属性
public int LChild
{
get
{
return lChild;
}
set
{
lChild = value;
}
}
//右孩子结点属性
public int RChild
{
get
{
return rChild;
}
set
{
rChild = value;
}
}
//父结点属性
public int Parent
{
get
{
return parent;
}
set
{
parent = value;
}
}
//构造器
public HNode()
{
weight = 0;
lChild = -1;
rChild = -1;
parent = -1;
}
}
哈夫曼树类HuffmanTree中只有一个成员方法Create,它的功能是输入n个叶子结点的权值,创建一棵哈夫曼树。其代码如下:
public class HuffmanTree
{
private HNode[] data; //结点数组
private int leafNum; //叶子结点数目
//索引器
public HNode this[int index]
{
get
{
return data[index];
}
set
{
data[index] = value;
}
}
//叶子结点数目属性
public int LeafNum
{
get
{
return leafNum;
}
set
{
leafNum = value;
}
}
//构造器
public HuffmanTree(int n)
{
data = new HNode[2 * n - 1];
for (int i = 0; i < 2 * n - 1; i++)
data[i] = new HNode();
leafNum = n;
}
//创建哈夫曼树
public void Create()
{
int m1, m2, x1, x2;
//输入n个叶子结点的权值
for (int i = 0; i < this.leafNum; ++i)
{
data[i].Weight = Convert.ToInt32(Console.ReadLine());
}
//处理n个叶子结点,建立哈夫曼树
for (int i = 0; i < this.leafNum - 1; ++i)
{
m1 = m2 = Int32.MaxValue;
x1 = x2 = 0;
//在全部结点中找权值最小的两个结点
for (int j = 0; j < this.leafNum + i; ++j)
{
if ((data[j].Weight < m1)
&& (data[j].Parent == -1))
{
m2 = m1;
x2 = x1;
m1 = data[j].Weight;
x1 = j;
}
else if ((data[j].Weight < m2)
&& (data[j].Parent == -1))
{
m2 = data[j].Weight;
x2 = j;
}
}
data[x1].Parent = this.leafNum + i;
data[x2].Parent = this.leafNum + i;
data[this.leafNum + i].Weight = data[x1].Weight + data[x2].Weight;
data[this.leafNum + i].LChild = x1;
data[this.leafNum + i].RChild = x2;
}
}
//测试哈夫曼树
public static void Main()
{
HuffmanTree ht;
Console.Write("请输入叶结点的个数:");
int leafNum = Convert.ToInt32(Console.ReadLine());
ht = new HuffmanTree(leafNum);
ht.Create();
Console.WriteLine("位置\t权值\t父结点\t左孩子结点\t右孩子结点");
for (int i = 0; i < 2 * leafNum - 1; i++)
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t\t{4}", i, ht[i].Weight, ht[i].Parent, ht[i].LChild, ht[i].RChild);
}
Console.ReadLine();
}
}