哈夫曼树/哈夫曼编码

经典的合并果子问题

有n堆果子,每堆果子的质量已知,现在需要把这些果子合并为一堆,但是每次都只可以把2堆果子合并为一堆。同时会消耗与2堆果子质量之和相同的体力。显然,在进行n-1次合并之后,只剩下一堆了。为了节省体力,请设计出合适的次序方案,使得耗费的体力最少,并给出体力值。

不妨把每堆果子看作节点,果堆的质量看作节点的权值,这样合并2个果堆相当于生成一个父节点,权值等于他们的质量之和,于是把n堆果子合并为一堆的过程可以用一棵树表示。

下图是合并5堆质量分别为1,2,2,3,6的果子的某一个方案:

所消耗的体力之和为4+6+8+14=32(非叶子节点的权值之和)也等于2*2+6*2+1*3+3*3+2*2=32(叶子节点的权值乘以各自路径长度再求和)

路径长度指叶子节点到根节点所经过的边数。

叶子节点的带权路径长度为叶子节点的权值乘以其路径长度的结果。

树的带权路径长度为所有叶子节点的带权路径长度之和。

哈夫曼树

已知n个数,寻找一棵树,使得树的所有的叶子节点的权值都为(恰为)这n个数,并使得这棵树的带权路径长度最小。带权路径长度最小的树被称为哈夫曼树(最优二叉树)。对于同一组数来说,最优二叉树不一定唯一,但是最小带权路径长度是唯一的。

构造哈夫曼树的算法:

(1)初始状态下有n个节点(节点的权值为给定的n个数),将他们视为n颗只有1个节点的树。

(2)合并其中根节点权值最小的2棵树,生成这2棵树根节点的父节点,节点的权值为2个根节点的权值之和,树的数量减少一个。

(3)重复操作2,直到只有1课树为止,这棵树就是哈夫曼树。

一般使用优先队列来执行这种策略。初始状态下把果堆质量压入优先队列(小顶堆),之后每次从优先队列的顶部取出2个最小的数,相加并重新压入优先队列,直至优先队列中只有1个数,此时就是消耗的最小体力。

#include <cstdio>
#include <queue>
#include <algorithm>

using namespace std;

//代表小顶堆的优先队列
priority_queue<long long,vector<long long>,greater<long long> > q;

int main()
{
    int n;
    long long temp,x,y,ans=0;
    scanf("%d",&n);

    for(int i=0;i<n;i++)
    {
        scanf("%lld",&temp);
        q.push(temp);
    }

    while(q.size()>1)
    {
        x=q.top();
        q.pop();
        y=q.top();
        q.pop();

        q.push(x+y);
        ans+=x+y;
    }

    printf("%lld\n",ans);
    return 0;
}

哈夫曼编码

对任意一颗二叉树,如果把二叉树上的所有分支都进行编号,将所有左分支都标记为0,所有右分支都标记为1,那么对于树上任意一个节点,都可以根据从根节点出发到达它的分支顺序得到一个编号,并且这个编号是所有编号中唯一的。

但对于任意一个非叶子节点,其编号一定是某个叶子节点的前缀。对于任意一个叶子节点,其编号一定不会成为任意一个节点编号的前缀。

假如有一个字符串,有A,B,C,D这四个英文字符的一个或多个组成,例如,ABCAD,现在希望把他编成一个01字符串,方便数据运输。加入有如下编码:

A:0 B:1 C:00 D:01

则ABCAD可以表示为:0100001但解码的时候就会发现无法知道01开头的是AB还是D。因为存在一种字符的编码是另一种字符编码的前缀。因此需要一种编码方式,使得任意字符的编码都不是另一个字符编码的前缀,即前缀编码。于是就会想到,依照前面说法,把这些字符作为一颗二叉树的叶子节点,就可以产生需要的编码。

如图是一种可行的前缀编码方式:A:00  B:01  C:100  D:101。前缀编码的存在意义在于不产生混淆,让解码正常进行。

对于一个给定的字符串来说,肯定有多种前缀编码方式。为了信息传递效率,需要尽量选择长度最短的编码方式。假如有一个字符串:ABACDBAABC共有10个字符,A出现了4次,B出现了3次,C出现2次,D出现了1次。如果按照以上的编码方式,编码长度为2*4+3*2+3*2+3*1=23.如果把ABCD的出现频次当作各自叶子节点的权值,那么字符编码成01串后的长度实际上就是这颗树的带权路径长度。

于是问题转化为:把每个字符出现的次数当作叶子节点的权值,求一棵树使得这棵树的带权路径长度最小。事实上这就是哈夫曼树。只要针对叶子节点权值为1,2,3,4建立哈夫曼树。这种由哈夫曼树产生的编码方式成为哈夫曼编码。显然哈夫曼编码是能使给定字符串编码为01字符串后长度最短的前缀编码。

上面的例子构造出的哈夫曼树为

编码方式为:A:0  B:10  C:110  D:111.

对于一个给定的字符串,其哈夫曼编码可能不唯一,但是其编码最短长度是唯一的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值