算法学习笔记——数据结构:哈夫曼树、带权路径长度WPL、哈夫曼编码

引入

合并果子问题如下:

有n堆果子,每次可以合并任意两堆果子,耗费体力值为[两堆果子数之和],最终在n-1次合并后,得到一堆果子。
给出合并的方案,使得耗费的体力值最小
例如有3堆果子,质量依次为1、2、9.那么可以先将质量为1和2的果堆合并,新堆质量为3,耗费体力为3;然后将3与9的果堆合并,得到12,耗费体力为12。所以耗费体力之和为3+12=15。可以证明15为最小的体力耗费值

  • 每次合并,都要将被合并的两堆果子数之和作为费用,显然,如果一开始就合并数量大的果堆,这个果堆会在接下来的合并过程中一直对费用有贡献,我们要避免这种情况
  • 我们应该尽可能减少数量大的果堆被合并的次数,即将其留到最后再合并
  • 因此,策略是:反复选择当前最小的两个元素,合并,直到仅剩一个元素
  • 具体实现可以用优先队列完成

这个问题,本质上是一个哈夫曼树的构造问题

哈夫曼树

我们用树来描述上面的合并过程:

假如过果堆为1,2,2,3,6,一种可能的合并过程如下:
在这里插入图片描述
计算整个合并过程的总花费:
①可以将所有非叶子节点的值求和
②也可以计算每个叶子节点对总花费的贡献:sum[叶子节点值 * 叶子节点到根节点的距离]

考虑方法②,我们引入几个术语
叶子节点的带权路径长度:叶子节点值 * 叶子节点到根节点的距离
树的带权路径长度WPL(Weighed Path Length of Tree):所有叶子节点的带权路径长度之和
哈夫曼树/最优二叉树:树的带权路径长度WPL最小的树

现在回到上面的合并果子问题

  • 原问题转化为:将n个数字作为叶子节点,构造一棵哈夫曼树(即带权路径长度最小的树),返回其最小带权路径长度
  • 考虑每个节点对答案的贡献,值越大的叶子节点,应该越接近根部

因此,哈夫曼树的构造思路:反复选择当前最小的两个元素,合并,直到仅剩一个元素

  1. 最初所有果堆看作叶子节点(视作仅有一个节点的树)
  2. 合并其中根节点值最小的两棵树,产生一个新的父节点,父节点权值=两个子节点权值之和,这三个节点构成一棵新的树
  3. 接下来继续从 余下的树的根节点 中,继续选择两个节点合并,重复第2步
  4. 最终只剩下一棵树,就是哈夫曼树

ps. 对于最小的树的带权路径长度WPL,可能有多种不同的构造方案,即:对于同一组叶子节点,哈夫曼树可以是不唯一的

总结

哈夫曼树的核心思想:

  • 考虑每个节点对于最终答案的贡献,其贡献与 [叶子节点值 * 叶子节点到根节点的距离(即带权路径长度)] 有关
  • 因此,值越大的叶子节点,应该越接近根部
  • 故应该优先合并当前权值最小的两个节点
  • 反复选择当前最小的两个元素,合并,直到仅剩一个元素
应用

LeetCode 1130. 叶值的最小代价生成树
同样要求构造二叉树,但是叶子节点从左到右的出现顺序给定,且每次合并两个节点,花费=左子树中的最大值*右子树中的最大值

  • 类似哈夫曼树的思想,值越大的叶子,应该越接近根部(这样其对花费的贡献最小)
  • 将叶子节点从左到右排列,每次取 [相邻两个元素的最大值] 最小的那两个节点,合并
  • 实现过程用数组模拟即可

哈夫曼编码

在传输数据时,可以将不同的字符转化为特定的01编码来传输,例如A为0,B为10,C为11等等,我们要求编码为前缀码,即任何一个字符对应的01串都不能是另一个字符对应的01串的前缀。(否则解码时,不知道某个01串应该翻译为什么字符)
对于一段特定的文本 ,其中的每个字符出现频率不同,我们对其进行前缀码编码,求编码后可能的最短01串长度

分析:

  • 如何满足“前缀码”的条件呢?
    假想二叉树上某节点,向左儿子的边为0,向右儿子的边为1,那么从根节点到各节点,都能获得其唯一的编码

例如在下图中,C的编码为100
在这里插入图片描述
不难发现,任意一个叶子节点,其编码一定不会称为其他节点的编码前缀
图中T节点不是叶子节点,进而T的编码是C的编码的前缀,不符合要求
因此,我们将所有字符都安排在叶子节点即可

  • 如何让编码后的文本01串最短呢?
    显然,出现频率越高的字符,其编码应该越短,这样最终编码后的01串中,这个字符的01编码对于总长度的贡献能尽量小
  • 把字符的出现频率作为其叶子节点的权值,则问题转化为求带权路径长度最小的哈夫曼树问题(字符出现频率/即权值*编码长度/即路径长度=字符对编码后的文本的贡献)
  • 策略:所有字符按照出现频率排序,每次取出一个出现频率最低的叶子节点,加入哈夫曼树中,并获得一个新的根节点
    在这里插入图片描述

这样由哈夫曼树产生的编码就是哈夫曼编码,显然哈夫曼编码就是一种能使特定字符串编码为01串后长度最短的前缀码
ps. 注意,哈夫曼编码是针对特定的字符串而言的,因为其构造需要根据不同字符的出现频率来确定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值