您的位置:首页 > 其它

哈夫曼树学习小记

2017-08-24 15:37 197 查看
B组又现我不会的神奇东西了……

定义:

现在有n个元素,每个元素有一个值。

你需要把这n个元素放在一棵二叉树的叶子节点上,规定每个元素的代价为它所在叶子节点的深度乘上它的值,哈夫曼树就是使总代价最小的这样一棵树。

运用:

据说哈夫曼树是一棵最佳判定树,什么意思呢?举一个实例来看看。

问题:

给出一群学生的成绩,要你判断每个学生的成绩情况(不同分数段有不同的评价)。

解法:

这是一道信息学入门题,一贯的做法是打一坨if。

理论上当然是O(n)的,因为判断非常少,但是假设分数段非常非常多,我们知道最坏情况要把每个if都走一遍,在大数据中这非常慢。

然而事实上成绩的分布并不是均匀的,有些分数段人多,有些分数段人少,我们可以通过改变if的先后顺序来起到优化的目的。

举个例子,下面的图片盗自这里

第一种构造方式:



第二种构造方式:



所以如果我们预先抽取一些数据知道了各个分数段的频数,把频数看作权值,建一棵哈夫曼树,按照哈夫曼树的顺序去判断,就可以大大优化时间。

构造:

首先需要明白的是,权值越大的深度肯定要越低,这个贪心显然。

动态规划做法:

先对元素从大到小排序。

设fi,j表示现在已经做了前i个元素,还空出来j个叶子节点的最小代价。

第一种转移显然,就是把第i+1个元素放到一个叶子节点上,fi+1,j−1=fi,j。

第二种转移就是把当前剩下的叶子节点都再生成出两个节点来,代价是把当前的剩下元素全部加深了一层,所以fi,2∗j=fi,j+∑nk=i+1ak。

Ans=min(fn,k)

贪心做法:

就和合并果子一毛一样,一开始有n堆果子,每堆果子的大小是元素的值。每次选最小的两堆果子合并,直到剩下一堆果子。每次合并可以看作将两堆果子上的元素深度加一,和dp做法不同的是,这是倒着做的,先确定了权值小的元素,然后将它们不断加深,自己感受一下即可。

哈夫曼树的代价就是合并果子的代价。

用个堆维护是O(n log n)的。

Code:

#include<set>
#include<cstdio>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 100005;

multiset<int> s;

int T, n, a
;
long long sum;

int main(){
for(scanf("%d", &T); T; T --) {
s.clear();
sum = 0;
scanf("%d", &n);
fo(i, 1, n) scanf("%d", &a[i]), s.insert(a[i]);
fo(i, 1, n - 1) {
int x = *s.begin();
s.erase(s.begin());
int y = *s.begin();
s.erase(s.begin());
sum += x + y; s.insert(x + y);
}
printf("%lld\n", sum);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: