赫夫曼树及其应用

美国数学家赫夫曼(David Huffman)1952年发明了一种压缩编码方法,并得到广泛应用。为了纪念他的成就,人们把他在编码中用到的特殊的二叉树叫做赫夫曼树,他的编码方法叫做赫夫曼编码

下面一段程序用来给学生考试成绩划分等级:

if (a < 60)

  b = "不及格";

else if (a < 70)

  b = "及格";

else if (a < 80)

  b = "中等";

else if (a < 90)

  b = "良好";

else

  b = "优秀";

这段程序的判断过程如图:

赫夫曼树及其应用

图T36

不过这样的判断算法效率可能有问题,因为对于一般的考卷,学生成绩在5个等级上的分布规律如下表:

分数

0 ~ 59

60 ~ 69

70 ~ 79

80 ~ 89

90 ~ 100

所占比例

5%

15%

40%

30%

10%

仔细观察,中等成绩(70 ~ 79)比例最高,其次是良好(80 ~ 89),不及格所占比例最少。于是把图T36中的表示判断过程的二叉树重新调整如下图:

赫夫曼树及其应用

 图T37

看起来判断效率肯定是提高了,但具体提高多少未知。下面就来看看赫夫曼先生是如何说的。

赫夫曼树的定义与原理

首先把上面两颗二叉树简化为叶子结点带权的二叉树(注:树结点之间的边相关的数叫做权(weight))。其中A表示不及格,B表示及格,C表示中等,D表示良好,E表示优秀。

赫夫曼树及其应用

图T38

从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。如上图中左边的二叉树,根节点到D的路径长度为4,而右边的二叉树根节点到D的路径长度为2。树的路径长度就是从根节点到每一结点的路径长度之和。上图中左边的二叉树的路径长度为1+1+2+2+3+3+4+4=20,右边的二叉树的路径长度为1+2+2+3+3+1+2+2=16。

如果考虑到带权的结点,结点的带权路径长度就是从该结点到根节点之间的路径长度与结点上权的乘积。树的带权路径长度就是树中所有叶子结点的带权路径长度之和。假设有n个权值{w1,w2,…wn},构造一棵有n个叶子结点的二叉树,每个叶子结点带权wk,每个叶子的路径长度为lk,则其中带权路径长度WPL最小的二叉树称作赫夫曼树最优二叉树。如图T38中左边二叉树的带权路径长度为WPL=5*1 + 15*2 + 40*3 + 30*4 + 10*4 = 315,右边二叉树的WPL=5*3 + 15*3 + 40*2 + 30*2 + 10*2 = 220。这样就可以看出右边的二叉树的性能要比左边的二叉树的性能高上很多。那右边的二叉树是否是最优的赫夫曼树呢?赫夫曼树是如何构造出来的呢?看看下面的解决办法:

1 先把有权值的叶子结点按照从小到大的顺序排列:A5,E10,B15,D30,C40。

2 取头两个最小权值的结点作为一个新结点N1的两个孩子,相对较小的是左孩子。新结点的权值为两个叶子权值的和。如下图:

赫夫曼树及其应用

 图T39

3 将N1替换A和E,新序列为:N115,B15,D30,C40。

4 重复步骤2,将N1与B作为新结点N2的两个孩子,N2的权值为15+15=30。如下图:

赫夫曼树及其应用

 图T40

5 将N2替换N1和B,新序列为:N230,D30,C40。

6 重复步骤2。将N2和D作为新结点N3的两个孩子,N3的权值为30+30=60,如下图:

赫夫曼树及其应用

图T41

7 将N3替换N2和D,新序列为:C40,N360。

8 重复步骤2,将C与N3作为新结点T的两个孩子,T是根节点,至此完成赫夫曼树的构造。如下图:

赫夫曼树及其应用

图T42

图T42中的二叉树的WPL = 40*1 + 30*2 + 15*3 + 10*4 + 5*4 = 205。经过上面步骤构造出来的二叉树就是最优的赫夫曼树。

由此得出赫夫曼树的构造方法描述:

1 根据给定的n个权值{w1,w2,…wn}构成n棵二叉树的集合F={T1,T2,…Tn},其中每棵二叉树Ti中只有一个带权为wi的根节点,其左右子树为空。

2 在F中选取两棵根节点的权值最小的树作为左右子树构造一棵新的二叉树,且新置的二叉树的根节点的权值为其左右子树根节点的权值之和。

3 在F中删除这两棵树,同时将新得到的二叉树加入F中。

4 重复2和3步骤,直到F只含一棵树为止,这棵树就是赫夫曼树。

赫夫曼编码

赫夫曼在研究这种最优二叉树时的主要目的是解决当年远距离通信(主要是电报)的数据传输的最优化问题。比如传输一串字符“BADCADFEED”,采用二进制数据表示,如下表:

字母

A

B

C

D

E

F

二进制字符

000

001

010

011

100

101

编码之后的二进制数据流为“001000011010000011101100100011”,对方接收时同样按照3位一组解码。现在假设这6个字母出现的频率不同,A 27%,B %8,C 15%,D 15%,E 30%,F 5%。下面将27、8、15、15、30、5分别作为A、B、C、D、E、F的权值构造赫夫曼树,如下图:

赫夫曼树及其应用

 图T43

将图T43中赫夫曼树的权值左分支改为0,右分支改为1,如下图:

 

赫夫曼树及其应用

图T44

现在将这6个字母用从根节点到叶子所经过路径的0或1来编码,得到的编码表如下:

字母

A

B

C

D

E

F

二进制字符

01

1001

101

00

11

1000

将“BADCADFEED”再次编码得到“1001010010101001000111100”,共25个字符,与之前编码得到的30个字符相比大约节约了17%的存储和传输成本。

在解码时,用同样的赫夫曼树,即发送方和接收方约定好同样的赫夫曼编码规则。当接收方接收到“1001010010101001000111100”时,比对图T44中的赫夫曼树,由1001正好走到字母B,如下图:

赫夫曼树及其应用

图T45

然后是01,则从根结点走到字母A,如下图:

赫夫曼树及其应用

 图T46

其余的字母也可相应成功解码。

仔细观察上面的赫夫曼编码表中各个字母的编码会发现,不存在容易与1001、1000混淆的10、100等编码。这就说明若要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,这种编码称作前缀编码

下面是赫夫曼编码的定义:

一般的,设需要编码的字符集为{d1,d2,…,dn},各个字符在电文中出现的次数或频率集合为{w1,w2,…,wn},以d1,d2,…dn作为叶子结点,以w1,w2,…wn作为相应叶子结点的权值来构造一棵赫夫曼树。规定赫夫曼树的左分支代表0,右分支代表1,则从根节点到叶子节点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是赫夫曼编码

Reference:

[1] 《大话数据结构》

 

你可能感兴趣的:(应用)