哈夫曼树学习小记

B组又现我不会的神奇东西了……

定义:

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

运用:

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

问题:

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

解法:

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

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

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

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


第一种构造方式:
哈夫曼树学习小记_第1张图片

第二种构造方式:
哈夫曼树学习小记_第2张图片


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

构造:

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

动态规划做法:

先对元素从大到小排序。
fi,j 表示现在已经做了前i个元素,还空出来j个叶子节点的最小代价。
第一种转移显然,就是把第i+1个元素放到一个叶子节点上, fi+1,j1=fi,j
第二种转移就是把当前剩下的叶子节点都再生成出两个节点来,代价是把当前的剩下元素全部加深了一层,所以 fi,2j=fi,j+nk=i+1ak
Ans=min(fn,k)

贪心做法:

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

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

Code:

#include
#include
#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[N];
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);
    }
}

你可能感兴趣的:(模版,哈夫曼树)