vlambda博客
学习文章列表

贪心算法之哈夫曼树和哈夫曼编码

一.引例

一般的学生成绩评定分为五个等级:优秀、良好、中等、及格、不及格,常见的代码描述如下:

可以把上面的代码流程画成下面的树状流程图

贪心算法之哈夫曼树和哈夫曼编码

由于大部分学生成绩都是出于70到90之间,所以按照上面的流程大部分人都需要至少三次判断。因此我们可以对评价流程做出一点优化,如下所示:

贪心算法之哈夫曼树和哈夫曼编码

通过先与80分做对比,使得大部分人(分数处于70-90之间)只需要两次判断

如何能设计出这种效率最高的树结构?答案就是哈夫曼树。



二.哈夫曼树

首先理解几个概念

1. 权:树中结点相关的数值;

2. 森林:m棵互不相交的树的集合;

3. 路径长度:从树中某个结点到另一个结点之间的分支数目(经过的边数);

4. 带权路径长度:从树的根结点到任意结点的路径长度(经过的边数)与该结点上权值的乘积称为该结点的带权路径长度。

 

树中所有叶结点的带权路径长度之和称为该树的带权路径长度,称为WPL(Weighted Path Length),且

贪心算法之哈夫曼树和哈夫曼编码

其中Wi是第i个叶结点所带的权值,Ii是该叶结点到根结点的路径长度

 

比如下面图a、图b、图c分别是三个构造好的不同形态的树,数值代表字母a、b、c、d出现的频率

贪心算法之哈夫曼树和哈夫曼编码

可以计算出

WPL(左)=7*2+5*2+2*2+4*2=36

WPL(中)=4*2+7*3+5*3+2*1=46

WPL(右)=7*1+5*2+2*3+4*3=35


哈夫曼树:含有N个带权叶子结点的二叉树中,WPL最小的二叉树,也称为最优二叉树。


哈夫曼树构造过程(哈夫曼算法):

1. 将N个结点分别作为N棵仅含一个结点的二叉树,构成森林F。

2. 构造一个新结点,并从F中选取两棵结点权值最小的树作为新结点的左右子树,并且将新结点的权值设置为左右子树结点的权值之和;

3. 从F中删除刚才选择的两棵树,同时将新得到的树加入F中;

4. 重复步骤2和3,直到F中只剩下一棵树为止。

 

举个例子:

对下图中的六个带权叶子结点来构造一棵哈夫曼树,步骤如下

贪心算法之哈夫曼树和哈夫曼编码

哈夫曼树的特点:

1. 每个初始结点最终成为叶结点,且权值大的结点用短路径,权值小的结点用长路径。

2. 构造过程中一共新建了N-1个结点,加上之前的N个结点,因此哈夫曼树中的结点总数为2N-1。

 

整体来说,哈弗曼算法属于贪心算法的一种,其基本思路是:编码以字符出现的频率作为权重,每次选权重最小的两个节点作为生成最优二叉树的左右孩子,并将权重之和作为根节点的权重(概率),自底向上生成一颗带权路径长度最短的最优二叉树。



三.哈夫曼编码

哈夫曼编码最早用于解决远距离电报通信的数据传输最优化问题

 

比如ABCDE这一段文字,通过网络传输要用到二进制的数字0和1。按照下面的方式保存ABCDE五个字母就可以了。

贪心算法之哈夫曼树和哈夫曼编码

但是如果信息很长,那么译码也会很长

例如 AABBCCDDEE...

对应 000 000 001 001 010 010 011 011 100 100...

 

实际上,不同的字符和字母出现的频率是不一样的,比如英语中的元音字母 A、E、I、O、U和汉字中的“有”、“是”、“在”...相对来说出现频率更高,所以我们可以借助这些特点和哈夫曼算法对信息进行编码

 

举例来说:

假设字母出现的频率为A:20,B:10,C:30,D:25,E:15,则对应的哈夫曼编码可以按照步骤得出:

1. 首先构造哈夫曼树

贪心算法之哈夫曼树和哈夫曼编码

2. 将权值左分支改为0,右分支改为1

贪心算法之哈夫曼树和哈夫曼编码

3. 每个字母的哈夫曼编码为从根结点走到相应叶子结点

贪心算法之哈夫曼树和哈夫曼编码


哈夫曼编码的优点

首先我们要知道三个概念

1. 等长编码

例如 AABBCCDDEE...

对应 000 000 001 001 010 010 011 011 100 100...

 

2.前缀编码

如果没有一个编码是另一个编码的前缀。

例如 A:0、B:101、C:100

就是一个前缀编码

 

3.非前缀编码

存在一个编码是另一个编码的前缀。

例如 A:10、B:101、C:110

就是一个非前缀编码

非前缀编码会造成翻译时的歧义

比如10110可以翻译成两个结果:

(1) 10 110  AC

(2) 101 10  BA

 

而哈夫曼编码是一种变长且前缀编码,对于同样的ABCDE进行编码,哈夫曼编码和等长编码结果如下:

等长编码一共有15位,哈夫曼编码一共有12位,节省约20%的空间成本,当信息内容很多时,哈夫曼编码的优势会更明显。



四.哈夫曼算法伪代码

算法:Huffman(C)
输入:C={x1,x2,...,xn},f(xi),i=1,2,3..n
输出:Q # 队列
  1.n <—— |C|
  2.Q <—— C # 频率递增队列Q
  3.for i <—— 1 to n-1 do
  4.    z <—— Allocate-None() # 生成结点z
  5.    z.left <—— Q中最小元 # 最小作z左结点
  6.    z.right <—— Q中最小元 # 最小作Z右结点
  7.    f(z) <—— f(x)+f(y)
  8.    Insert(Q,z) # 将z插入Q
  9.return Q

END

往期回顾