目录
基本概念
问题:给定n个权值,作为n个叶结点,构造一棵二叉树,而这棵树的特点是,有n个叶节点,叶节点的值为给定的权值。而内部节点的值为子树的权值和
带权路径:
哈夫曼树是这种二叉树中带权路径最小的
构造方法
设有n个权值,W={W1,W2,...Wn},各自构成一个根为自己的二叉树
选择权值最小的两个Wi,Wj,从W中删除Wi和Wj,合并为一棵根为Wi+Wj的二叉树,加入W,即W=W-{Wi,Wj}+{Wi+Wj}
当只剩下一个的时候,结束,剩下的那个二叉树,就是哈夫曼树
证明
引理:假设W1和W2是最小的两个权值,则他们一定是深度最深的分支节点的孩子
证明:假设不是W1和W2,而是Wx和Wy,设W1和W2的深度为p(或者p1和p2),Wx和Wy的深度为q,显然p<=q,且W1,W2<=Wx,Wy
WPL1=X+p*(W1+W2)+q*(Wx+Wy) (X是除了这4个节点以外的带权路径)
交换W1和Wx,W2和Wy
WPL2=X+q*(W1+W2)+p*(Wx+Wy)
WPL1-WPL2=(p-q)(W1+W2)-(p-q)*(Wx+Wy)=(p-q)*(W1+W2-Wx-Wy)>=0
即交换后更小,矛盾
得证
接着用归纳法证明这个算法成立:
n=2时显然成立
假设n=k时成立
当n=k+1时,根据引理,将最小的两个权值W1+W2合并成W1+W2,那这时候问题就转化成n=k时的情况,所以成立
代码
poj3253
其实就是搞个小根堆,然后每次弹两个最小的,加一下,放回去
#include<iostream>
#include<queue>
using namespace std;
int main(){
typedef long long int LL;
priority_queue<LL,vector<int>,greater<LL> > q;
int n;
scanf("%d",&n);
while(n--){
LL t;
scanf("%lld",&t);
q.push(t);
}
LL ans=0;
while(q.size()>1){
LL x=q.top();
q.pop();
LL y=q.top();
q.pop();
ans+=x+y;
q.push(x+y);
}
printf("%lld\n",ans);
return 0;
}
k叉哈夫曼树
k叉哈夫曼树是一个度只有0或者n的树,意味着
所以假设n个叶子,则n-1要被k-1整数,所以如果不能整除要补上一些虚节点,权值为0。
需要补 个
构造方法和2叉的时候一样,每次选k个最小的
#include<iostream>
#include<queue>
#include<algorithm>
#include<functional>
using namespace std;
const int N=100005;
int a[N],n;
int k_huff(int k){
int cost=0;
priority_queue<int,vector<int>,greater<int> > q;
for(int i=0;i<n;++i)q.push(a[i]);
bool first=true;
while(q.size()>1){
int cnt;
if(!first){
cnt=k;
}
else{
first=false;
if((n-1)%(k-1)==0){
cnt=k;
}
else{
cnt=(n-1)%(k-1)+1;
}
}
int sum=0;
while(cnt--){
sum+=q.top();
q.pop();
}
cost+=sum;
q.push(sum);
}
return cost;
}
例题
hdu5884
这题简单来说就是问WPL小于等于指定数的k叉哈夫曼树最小的k是多少
这题如果你一次一次循环上去,复杂度是
如果二分,复杂度是
所以我们换一种方法
假设权值放在a数组里,我们再开一个数组b
对a数组排序
用两个指针,一个指向a,一个指向b,然后哪个小,取哪个
选择出节点后,加入b数组,有点类似于归并
复杂度是
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100005;
int a[N],b[N],n;
bool k_huff(int k,int cost){
int i=0;
int j=0,b_size=0;
bool first=true;
while(b_size-j+n-i>1){
int cnt;
if(!first)cnt=k;
else{
first=false;
if((n-1)%(k-1)==0){
cnt=k;
}
else{
cnt=(n-1)%(k-1)+1;
}
}
int sum=0;
while(cnt--){
if(i==n)sum+=b[j++];
else if(j==b_size)sum+=a[i++];
else if(a[i]<=b[j])sum+=a[i++];
else sum+=b[j++];
}
cost-=sum;
if(cost<0)return false;
b[b_size++]=sum;
}
return true;
}
int main(){
int t,T;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&T);
for(int i=0;i<n;++i)scanf("%d",&a[i]);
if(n<=1){
printf("1\n");
}
else{
sort(a,a+n);
int low=2,high=n;
while(low<=high){
int mid=(low+high)>>1;
if(k_huff(mid,T)){
high=mid-1;
}
else{
low=mid+1;
}
}
printf("%d\n",high+1);
}
}
return 0;
}