【算法详解】Huffman树学习笔记

Huffman树学习笔记

H u f f m a n Huffman Huffman树用于解决这一类问题:构造一个 n n n个叶子节点的 k k k叉树,其中第 i i i个叶子节点带有权值 w i w_i wi,要求最小化 ∑ w i ∗ l i \sum w_i*l_i wili,其中 l i l_i li表示第 i i i个叶子节点到根节点的距离。

很显然的一个贪心做法:

为了最小化 ∑ w i ∗ l i \sum w_i*l_i wili,应该让权值大的叶子节点的深度尽量小。

k = 2 k=2 k=2时,我们可以用如下方法构造二叉 H u f f m a n Huffman Huffman树:

1、建立一个小根堆,插入这 n n n个叶子节点的权值

2、从堆中取出最小的两个权值 w 1 w_1 w1 w 2 w_2 w2,令 a n s + = w 1 + w 2 ans+=w_1+w_2 ans+=w1+w2

3、建立一个权值为 w 1 + w 2 w_1+w_2 w1+w2的树节点 p p p,使 p p p成为两个叶子结点的父节点,并在堆中插入 p p p

最后 a n s ans ans就是 ∑ w i ∗ l i \sum w_i*l_i wili的最小值

我们将问题拓展,对于 k k k H u f f m a n Huffman Huffman树,我们依样画葫芦可以构造出这样的算法:

1、建立一个小根堆,插入这 n n n个叶子节点的权值

2、从堆中取出最小的 k k k个权值 w 1 , w 2 … … w k w_1,w_2……w_k w1,w2wk,令 a n s + = ∑ i = 1 k w i ans+=\sum_{i=1}^{k}w_i ans+=i=1kwi

3、建立一个权值为 ∑ i = 1 k w i \sum_{i=1}^{k}w_i i=1kwi的树节点 p p p,使 p p p成为 k k k个叶子结点的父节点,并在堆中插入 p p p

但是,这样做下去是无法保证达到最优解的。

因为在最后一轮执行的时候,无法保证一共有 k k k个节点刚好合并成一个节点,也就是说最后的根节点不一定有 k − 1 k-1 k1个儿子。这样答案显然不是最优的,因为我们可以在底下随便那一个节点作为根节点的儿子,这样也可以让答案最优。

所以,为了让答案最优,我们应该在执行上述贪心算法之前,补加一些额外的权值为0的节点,是叶子结点总数n使得 ( n − 1 ) % ( k − 1 ) = 0 (n-1)\%(k-1)=0 (n1)%(k1)=0,意思是,我们让节点不足的情况放在最底层而不是最高层,这样做出来的做法显然是最优的。


荷马史诗

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cMiQSNmk-1607157358975)(C:\Users\Stuedent\AppData\Roaming\Typora\typora-user-images\image-20201029102917160.png)]

每个单词的数量就是每个节点的权值。

按照上述模板做一个 k k k H u f f m a n Huffman Huffman的模板即可。

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 2e5+10;
#define  gc getchar()
int n,k;
int ans = 0;
typedef pair < int , int > pii;
priority_queue < pii , vector < pii > , greater < pii > > q;
int read(){
	int s = 0 , f = 0; char ch = gc;
	while (ch < '0' || ch > '9') f|=ch=='-' , ch = gc;
	while (ch >= '0' && ch <= '9') s = s*10+ch-48 , ch = gc;
	return f?-s:s;
}

signed main(){
	n = read() , k = read();
	for (int i = 1,x; i <= n; i++) x = read() , q.push({x,1});
	while ((n-1)%(k-1)!=0) ++n,q.push({0,1});
	while (q.size()>=k){
		int w = 0 , h = 0;
		for (int i = 1; i <= k; i++)
		  w+=q.top().first , h = max(q.top().second,h),q.pop();
		q.push({w,h+1}); ans+=w;
	}
	printf("%lld\n%lld",ans,q.top().second-1);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值