哈夫曼树
Posted gcfer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈夫曼树相关的知识,希望对你有一定的参考价值。
先给出哈夫曼树的定义:构造一颗包含n个节点的k叉树其中每个叶子节点都有权值w[i],要求最小化所有叶子节点的w[i]*deep[i]之和.该问题的解被称为k叉哈夫曼树.
先来说两个引理:
1.权值最小的节点深度必定最大.
证明:我们设x,y.使得w[x]>w[y].但deep[x]>deep[y]如若交换x,y由于y的去权值更小,所以只会使得答案更优.所以只要存在权值大,深度大的节点,我们便可以通过交换使得答案
更优.
2.满足最优子结构
证明:我们可以将一些点合并之后,权值定义为这些点的权值和.我们发现最优解一定遵从引理1.即如果有深度,权值大于我们合并之后的点的权值与深度.我们照样可以将通过交换使得答案更小.且我们已经通过合并,此时权值是合并的点的和,发现这样对答案并无影响.
重要的是哈夫曼树的解法。
由于满足最优子结构,我们可以构造一个贪心算法,又由于满足引理1,我们可以很轻松的想到每次找出最小的若干个节点将他们合并,再将他们放回原来的集合中即可.
发现这可以用小根堆优化.
接下来有两道例题:
这个算是很水的题了,但仍要知其所以然..
我们考虑所有的合并操作实际上构成了什么,一棵树,n-1次操作实际上是树边.
之后考虑怎样产生的答案,考虑一个果子,他虽然合并成其他果子,但他所在的果子每次合并都是累加下它的代价,由于合并操作实际上就是树边,我们可以想到代价就是权值*操作次数=权值*树边,符合哈夫曼树的定义.由于每次只能合并两堆果子,所以这是一个满二叉树.
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,w[N],sum; priority_queue<int>q; int main() { cin>>n; for(int i=1;i<=n;++i) cin>>w[i],q.push(-w[i]); for(int i=1;i<n;++i) { int x1=-q.top();q.pop(); int x2=-q.top();q.pop(); sum+=x1+x2; q.push(-(x1+x2)); } cout<<sum<<endl; return 0; }
2.荷马史诗
很巨的题,题目意思真的饶...
我们考虑题面要求我们用k进制串表示若干个单词,使得总长度最小,且没有一个串是另一个串的前缀.
我们考虑tire树,不能有前缀的意思就是每个单词必须是叶子节点,由于是k进制数,所以tire树的最大分支为k.考虑答案的组成.
每个单词的权值*代表单词的串的长度.而串的长度在tire树种就是到根节点的距离.嗯又是满足哈夫曼树的定义.
由于未必是满k叉树,所以我们可以加一些0节点,使得总结点数能构成满k叉树.因为这些0节点一定在最深的,且对答案不造成贡献.
这里顺便说一下,叶子节点满足(n-1)%(k-1)==0时才是满二叉树...
#include<bits/stdc++.h> #define ll long long using namespace std; int n,k; priority_queue<pair<ll,int> >q; int main() { cin>>n>>k; for(int i=1;i<=n;++i) { ll x;cin>>x; q.push({-x,0}); } while((n-1)%(k-1)!=0) q.push({0,0}),++n; ll ans=0;int depth=0; while(q.size()>1) { ll sur=0;int maxdepth=0; for(int i=1;i<=k;++i) { sur+=-q.top().first; maxdepth=max(maxdepth,-q.top().second); q.pop(); } q.push({-sur,-maxdepth-1}); ans+=sur;depth=max(maxdepth+1,depth); } cout<<ans<<endl<<depth<<endl; return 0; }
以上是关于哈夫曼树的主要内容,如果未能解决你的问题,请参考以下文章