最优二叉树——哈夫曼树
【引入】
在实际应用中,常常要考虑一个问题:如何设计一棵二叉树,使得执行路径最短,即算法的效率最高。
7.1快递包裹的邮资问题
假设邮政局的包裹自动测试系统能够测出包裹的重量,如何设计一棵二叉树将包裹根据重量及运距进行分类从而确定邮资。
国内快递包裹资费 单位:元
(2004年1月1日起执行)
运距(公里)
首重1000
5000克以内续重每500
5001克以上续重每500
<=500
5.00
2.00
1.00
<=1000  >500
6.00
2.50
1.30
<=1500  >1000
7.00
3.00
1.60
<=2000  >1500
8.00
3.50
1.90
<=2500  >2000
9.00
4.00
2.20
<=3000  >2500
10.00
4.50
2.50
<=4000  >3000
12.00
5.50
3.10
<=5000  >4000
14.00
6.50
3.70
<=6000  >5000
16.00
7.50
4.30
>6000
20.00
9.00
6.00
表7.1 国家邮政局制定的快递包裹参考标准
根据表7.1可以制定出许多种二叉树,但不同的二叉树判定的次数可能不一样,执行的效率也不同。
7.2 铁球分类
现有一批球磨机上的铁球,需要将它分成四类:直径不大于20的属于第一类;直径大于20而不大于50的属于第二类;直径大于50而不大于100的属于第三类;其余的属于第四类;假定这批球中属于第一、二、三、四类铁球的个数之比例是1:2:3:4
我们可以把这个判断过程表示为 7.1中的两种方法:
7.1 两种判断二叉树示意图
那么究竟将这个判断过程表示成哪一个判断框,才能使其执行时间最短呢?让我们对上述判断框做一具体的分析。
假设有1000个铁球,则各类铁球的个数分别为:100200300400
对于图7.1中的左图和右图比较的次数分别如表7.2所示:
左图
右图
序号
比较式
比较次数
序号
比较式
比较次数
1
a<=20
1000
1
a>100
1000
2
a<=50
900
2
a>50
600
3
a<=100
700
3
a<=20
300
合计
2600
合计
1900
7.2 两种判断二叉树比较次数
过上述分析可知,图7.1中右图所示的判断框的比较次数远远小于左图所示的判断框的比较次数。为了出比较次数最少的判断框,将涉及到树的路径长度问题。
7.1哈夫曼树的基本概念
    最优二叉树,也称哈夫曼(Haffman)树,是指对于一组带有确定权值的叶结点,构造的具有最小带权路径长度的二叉树。
    那么什么是二叉树的带权路径长度呢?
    在前面我们介绍过路径和结点的路径长度的概念,而二叉树的路径长度则是指由根结点到所有叶结点的路径长度之和。如果二叉树中的叶结点都具有一定的权值,则可将这一概念加以推广。设二叉树具有n个带权值的叶结点,那么从根结点到各个叶结点的路径长度与相应结点权值的乘积之和叫做二叉树的带权路径长度,记为:
n
k=1
                    WPL=  Wk·Lk
    其中Wk为第k个叶结点的权值,Lk 为第k个叶结点的路径长度。如图7.2所示的二叉树,它的带权路径长度值WPL=2×2+4×2+5×2+3×2=28。
    在给定一组具有确定权值的叶结点,可以构造出不同的带权二叉树。例如,给出4个叶结点,设其权值分别为1,3,5,7,我们可以构造出形状不同的多个二叉树。这些形状不同的二叉树的带权路径长度将各不相同。图7.3给出了其中5个不同形状的二叉树。
    这五棵树的带权路径长度分别为:
    (a)WPL=1×2+3×2+5×2+7×2=32
3
5
4
2
weight的所有形式    (b)WPL=1×3+3×3+5×2+7×1=29
    (c)WPL=1×2+3×3+5×3+7×1=33
    (d)WPL=7×3+5×3+3×2+1×1=43            7.2  一个带权二叉树
(e)WPL=7×1+5×2+3×3+1×3=29
7
7
5
3
1
1
7
5
3
1
5
3
        (a)                          (b)                      (c)
7
1
5
3
5
7
3
1
                  (d)                          (e)
7.3  具有相同叶子结点和不同带权路径长度的二叉树
由此可见,由相同权值的一组叶子结点所构成的二叉树有不同的形态和不同的带权路径长度,那么如何到带权路径长度最小的二叉树(即哈夫曼树)呢?根据哈夫曼树的定义,一棵二叉树要使其WPL值最小,必须使权值越大的叶结点越靠近根结点,而权值越小的叶结点越远离根结点。
哈夫曼(Haffman)依据这一特点1952提出了一种方法,这种方法的基本思想是:
    (1)由给定的n个权值{W1,W2,…,Wn}构造n棵只有一个叶结点的二叉树,从而得到一个二叉树的集合F={T1,T2,…,Tn};
    (2)在F中选取根结点的权值最小和次小的两棵二叉树作为左、右子树构造一棵新的二叉树,这棵新的二叉树根结点的权值为其左、右子树根结点权值之和;
    (3)在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到集合F中;
(4)重复(2)(3)两步,当F中只剩下一棵二叉树时,这棵二叉树便是所要建立的哈夫曼树。
由于这种算法是哈夫曼最早提出的,所以将最优二叉树称为哈夫曼树。
图7.4给出了前面提到的叶结点权值集合为W={1,3,5,7}的哈夫曼树的构造过程。可以计算出其带权路径长度为29,由此可见,对于同一组给定叶结点所构造的哈夫曼树,树的形状可能不同,但带权路径长度值是相同的,一定是最小的。
4
  第一步                            第二步
5
7
1
3
5
7
3
1
  第三步                            第四步
16
9
7
9
4
7
5
4
1
3
5
1
3
7.4  哈夫曼树的建立过程
7. 2 哈夫曼树的构造算法
从上述算法中可以看出,F实际上是森林,该算法的思想是不断地进行森林F中的二叉树的“合并”,最终得到哈夫曼树。
算法一:
在构造哈夫曼树时,可以设置一个结构数组HuffNode保存哈夫曼树中各结点的信息,根据二叉树的性质可知,具有n个叶子结点的哈夫曼树共有2n-1个结点,所以数组HuffNode的大小设置为2n-1,数组元素的结构形式如下:
rchild
weight
lchild
parent
其中,weight域保存结点的权值,lchild和rchild域分别保存该结点的左、右孩子结点在数组HuffNode中的序号,从而建立起结点之间的关系。为了判定一个结点是否已加入到要建立的哈夫曼树中,可通过parent域的值来确定。初始时parent的值为-1,当结点加入到树中时,该结点parent的值为其双亲结点在数组HuffNode中的序号,就不会是-1了。
构造哈夫曼树时,首先将由n个字符形成的n个叶结点存放到数组HuffNode的前n个分量中,然后根据前面介绍的哈夫曼方法的基本思想,不断将两个小子树合并为一个较大的子树,每次构成的新子树的根结点顺序放到HuffNode数组中的前n个分量的后面。
下面给出哈夫曼树的构造算法。
    const maxvalue= 10000;      {定义最大权值}
        maxleat=30;          {定义哈夫曼树中叶子结点个数}
        maxnode=maxleaf*2-1;
    type HnodeType=record
                  weight: integer;
                  parent: integer;
                  lchild: integer;
                  rchild: integer;
                  end;
  HuffArr:axnode] of HnodeType;
var ……
procedure CreatHaffmanTree(var HuffNode: HuffArr); {哈夫曼树的构造算法}
var i,j,m1,m2,x1,x2,n: integer;
begin
readln(n);            {输入叶子结点个数}
for i:=0 to 2*n-1 do    {数组HuffNode[ ]初始化}
  begin
    HuffNode[i].weight=0;
    HuffNode[i].parent=-1;
    HuffNode[i].lchild=-1;
    HuffNode[i].rchild=-1;
  end;
for i:=0 to n-1 do read(HuffNode[i].weight); {输入n个叶子结点的权值}
for i:=0 to n-1  do            {构造哈夫曼树}
  begin
m1:=MAXVALUE; m2:=MAXVALUE;
    x1:=0; x2:=0;
    for j:=0 to n+i-1 do
    if  (HuffNode[j].weight<m1) and (HuffNode[j].parent=-1) then
      begin  m2:=m1;    x2:=x1;
            m1:=HuffNode[j].weight;    x1:=j;
end
else if (HuffNode[j].weight<m2) and (HuffNode[j].parent=-1) then
            begin m2:=HuffNode[j].weight; x2:=j; end;
{将出的两棵子树合并为一棵子树}
      HuffNode[x1].parent:=n+i;  HuffNode[x2].parent:=n+i;
      HuffNode[n+i].weight:= HuffNode[x1].weight+HuffNode[x2].weight;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。