哈夫曼树&拓展

哈夫曼树学习笔记

1.简介

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 ——百度百科

也就是说,哈夫曼树是解决带权路径长度最短的好方法。

我们不妨用一个简单的问题来对哈夫曼树有更深入的理解


有n个元素,要求用2进制串表示这n个元素,且任何一个元素不是另一个元素的前缀,请问最短编码长度是?


每个元素都不是其他的元素的前缀

也就是说,每个元素都应该是一片叶子,可以把这个问题转化成一个树上路径问题。如果要求WPL最小,那么权值大的结点应该离根更近。但如何安排这些节点呢?

2.实现

我们希望点权值最大的离根尽可能近,而权值小的则会远一点。

我们可以从n个元素中,每次取出2个权值最小的节点A,B,并生成一个新的节点C,在这棵树上,C就是A,B的爸爸,c的权值为AB权值之和。将新生成的节点也放回元素集合中。

每次取最小值可以由优先队列进行优化,将复杂度由 O( n 2 n^{2} n2 ) 提升到 O( n l o g n n logn nlogn)

3.拓展——多叉哈夫曼树

好了,你已经掌握了哈夫曼树,那么请试试下面这个问题吧

[洛谷P2168 荷马史诗]([P2168 NOI2015] 荷马史诗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

我们能够感觉到其中贪心的味道,而且是WPL最短问题。

k进制表示这棵树是k叉的

也就是说,每次合成新的节点需要取优先队列中的前k个点

如果试试,你就会发现,喜提WA

Why?

多叉树在构建时,如果最后一次合并节点数已不足k个,那也只能合成1个节点,而根节点的儿子数不足k,显然不是最优。

因此,在构建树前,要加入m个权值为0的节点(0对WPL无贡献,WPL问题也不太可能出现节点权值为负数吧)

所以思路就有了——如果是2进制,就直接按流程计算;如果是k进制,先补充0节点,再按照流程做。

粘一份代码qwq (俺英语不好,注释如果写的有问题,轻喷)

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;

int n,k;
long long sum_val,sum_points;
struct poin{
	long long sum;
	int depth;
	friend bool operator<(const poin &a,const poin &b){
		if(a.sum!=b.sum)	return a.sum>b.sum;
		else if(a.sum==b.sum) return a.depth>=b.depth;
	}
}poi[100005];

priority_queue<poin> q;
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%lld",&poi[i].sum);
		poi[i].depth=1;
		q.push(poi[i]);
	}
	if(k==2){// just the typical huffman tree
		
		while(!q.empty()){
			poin point_1st=q.top();
			q.pop();
			poin point_2nd=q.top();
			q.pop();
			poin *tmp=new poin;
			tmp->sum=point_1st.sum+point_2nd.sum;
			tmp->depth=max(point_1st.depth,point_2nd.depth)+1;
			sum_points+=tmp->sum;
			q.push(*tmp);
			
			if(q.size()==1)break;
		}
		
	}
	else{// there's an circumstance that you need to add the poi whose val is 0 to better the huffman tree,so you need to handle this circumstance
		while((n-1)%(k-1)!=0){
			poin *tmp=new poin;
			tmp->depth=1;
			tmp->sum=0;
			q.push(*tmp);
			n++;
		}
		
		while(!q.empty()){
			long long new_sum=0;
			int new_depth=0;
			for(int i=1;i<=k;i++){
				poin tmp=q.top();
				q.pop();
				new_sum+=tmp.sum;
				new_depth=max(new_depth,tmp.depth+1);
			}
			poin *tmp=new poin;
			tmp->depth=new_depth;
			tmp->sum=new_sum;
			sum_points+=new_sum;
			q.push(*tmp);
			if(q.size()==1)break;
		}	
	}
	poin ans=q.top();
	q.pop();
	printf("%lld\n%d",sum_points,ans.depth-1);
	return 0;
}
  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值