Loading

哈夫曼树和哈夫曼编码

哈夫曼树

是一颗二叉树,又称为最优二叉树。它的叶子节点到根节点的带权路径和最小

在这里,带权路径=一个节点的权值*该节点到另一个节点的边的数量

构建哈夫曼树

给定\(n\)个权值为\(w\)的节点

我们在其中选出权值最小的两个点取出,假设为\(w_i,w_j\),然后再新建一个权值为\(w_i+w_j\)的节点重新放入

反复直到剩下一个节点,一棵树就好了

显然,大的节点在上面,小的节点在下面,正确性不难证明

哈夫曼编码

假设我们有一个字符串IAKIOI

这个字符串的总bit数为48,我们能不能把它压缩一下呢?

哈夫曼编码就是一种无损压缩方法,它的思想是:出现频率越少的字符编码越短,频率越高字符编码越长

但是有压缩就必然有解压,如何保证解压的正确率呢?

我们需要保证每一个编码都不能是另一个编码的前缀,可以联想到树的叶子结点也有相似的性质

实现

我们结合上面的哈夫曼树,将每种字符出现的次数当做该字符的权值,构建一颗哈夫曼树

然后对于每一棵子树,左边为0, 右边为1,叶子节点到根的路径就是它的哈夫曼编码

我们又可以发现,压缩后的字符串的长度就是上图中的蓝色节点的权值和

这是因为在不断合并的过程中,有一些节点的权值不断地相加。

然后再将它们加起来,答案就出来了

例题

洛谷P2168荷马史诗

这道题目我们可以把哈夫曼树改为多叉的,然后就是模板了

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const int inf = 1 << 26;
struct trees {
    LL v, h;
};
bool operator < (trees a, trees b){
    if(a.v != b.v) return a.v > b.v;
    return a.h > b.h;    
}
priority_queue<trees> q;
int n, k;
int main(){
    scanf("%d %d", &n, &k);
    for(int i = 1; i <= n; i ++) {
        trees a;
        scanf("%lld", &a.v);
        a.h = 1;
        q.push(a);
    }
    LL top = 0;
    if((n - 1) % (k - 1) != 0) top += k - 1 - (n - 1) % (k - 1);
    for(int i = 1; i <= top; i ++) {
        trees need;
        need.v = 0; need.h = 1;
        q.push(need);
    } 
    top += n;
    LL ans = 0;
    while(top != 1) {
        trees a;
        LL temp = 0, mx = 0;
        for(int i = 1; i <= k; i ++) {
            a = q.top(); temp += a.v;
            mx = max(mx, a.h); q.pop();
        }
        ans += temp;
        a.v = temp; a.h = mx + 1;
        q.push(a);
        top -= k - 1;
    }
    printf("%lld\n%lld\n", ans, q.top().h - 1);
    return 0;
}
posted @ 2021-08-07 17:53  zhangwenxuan  阅读(192)  评论(0编辑  收藏  举报