【知识点8】哈夫曼树⭐⭐⭐⭐⭐

本文深入探讨了哈夫曼树的构造及其在编码中的应用。介绍了如何通过哈夫曼树解决合并果子问题,实现最小带权路径长度。同时,详细讲解了哈夫曼编码的生成过程,以及其在信息压缩中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

哈夫曼树

问题引入——合并果子

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

  • 带权路径长度:叶子结点的权值乘以其路径长度的结果;
  • 树的带权路径长度(Weighted Path Length of Tree,WPL):树的所有叶子结点的带权路径长度之和。

于是,我们合并果子的问题就转换成:

  • 已知n个数,寻找一棵树,使得树的所有叶子结点的权值恰好为这n个数,并且使得这棵树的带权路径长度最小。

带权路径长度最小的树称为哈夫曼树(又称最优二叉树)。显然,对同一组叶子结点来说,哈夫曼树可以是不唯一的,但是最小带权路径长度一定是唯一的(想一想为什么?)

哈夫曼树的构造

下面给出一个非常简洁易操作的算法,来构造一棵哈夫曼树:

  1. 初始状态下共有n个结点(结点的权值分别是给定的n个数),将它们视作n棵只有一个结点的树。
  2. 合并其中根结点权值最小的两棵树,生成两棵树根结点的父结点,权值为这两个根结点的权值之和,这样树的数量就减少一个。
  3. 重复操作2,直到只剩下一棵树为止,这棵树就是哈夫曼树。

以前面的例子为例,初始状态下有5个果堆,质量分别是1、2、2、3、6,当前状态如图9-60所示:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
通过这个例子也可以发现,对哈夫曼树来说不存在度为1的结点,并且权值越高的结点相对来说越接近根结点。

关于上述算法的正确性可以参考算法导论

在很多实际场景中,不需要真的去构建一棵哈夫曼树,只需能得到最终的带权路径长度即可(例如合并果子问题就只需要知道消耗的最小体力),因此读者需要着重掌握的是哈夫曼树的构建思想,也就是反复选择两个最小的元素,合并,直到只剩下一个元素。于是,一般可以使用优先队列(也可以说是堆结构)来执行这种策略。

以合并果子问题为例,初始状态下把果堆的质量压入优先队列(注意含义为小顶堆),之后每次从优先队列顶部取出两个最小的数,将它们相加并重新压入优先队列(需要在外部定义一个变量ans,将每次相加的结果累加起来),重复直到优先队列中只剩下一个数,此时得到了消耗的最小体力ans,并且方案也可以在这个过程中得到。

以合并果子为例,可以直接使用优先队列来实现。代码如下:

#include<cstdio>
#include<queue>

using namespace std;

//代表小顶堆的优先队列
priority_queue<long long,vector<long long>,greater<long long>> q;

int main(){
	int n;
	long long temp,x,ans = 0;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%lld",&temp);
		q.push(temp);	//将初始重量压入优先队列 
	}
	
	while(q.size() > 1){	//只要优先队列中至少有两个元素 
		x = q.top();
		q.top();
		y = q.top();
		q.pop();
		q.push(x+y);	//取出堆顶的两个元素,求和后压入优先队列 
		ans += x+y;		//累计求和的结果 
	} 
	printf("%lld\n",ans); 
	return 0;
} 

哈夫曼编码

在这里插入图片描述在这里插入图片描述
因此需要寻找一套编码方式,使得其中任何一个字符的编码都不是另一个字符的编码的前缀,同时把满足这种编码方式的编码称为前缀编码

于是很快就会想到,依照本节一开始的说法,只要让这些字符作为一棵二叉树的叶子结点,就能产生需要的编码。因此可以让A用00表示,B用01表示,C用100表示,D用101表示,就可以把ABCAD编码成000110000101,并且不会产生混淆,如图9-66所示。再次强调,前缀编码的存在意义在于不产生混淆,让解码能够正常进行。
在这里插入图片描述
考虑进一步的问题。对一个给定的字符串来说,肯定有很多种前缀编码的方式,但是为了信息传递的效率,需要尽量选择长度最短的编码方式。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述
最后再次强调,哈夫曼编码是针对确定的字符串来讲的。只有对确定的字符串,才能根据其中各字符的出现次数建立哈夫曼树,于是才有对应的哈夫曼编码。哈夫曼编码需要构建具体的哈夫曼树。(等我刷到这样的题,我就回来补代码~~~)

参考资料

算法笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值