[算法]Huffman树(哈夫曼树)

[算法]Huffman树(哈夫曼树)

一、关于Huffman树

Huffman树(哈夫曼树)可以解决下述问题:

  • 一颗\(n\)个叶节点的\(k\)叉树,第\(i\)个叶节点的权值为\(w_i\),现在欲求\(\sum w_i\times l_i\)的最小值,其中\(l_i\)表示第\(i\)个叶子节点到根结点的距离。

二、具体实现

为了保证\(\sum w_i\times l_i\)最小,我们应该保证权值越大的叶节点深度越小。可以看出,这是很简单的贪心思想。
特殊地,我们可以先从二叉Huffman树开始研究。二叉Huffman树的实现过程如下:

1.构造一个小根堆,依次插入这\(n\)个节点的权值。
2.从堆内依次取出权值最小的两个节点\(w_1,w_2\),令\(ans+=w_1+w_2\)。
3.把\(w_1+w_2\)作为新的节点\(w_3\),并插入到堆中。此时\(w_3\)为\(w_1,w_2\)的父亲节点。
4.重复上述操作,直到堆的大小等于1。

Huffman树并没有真正的建立一棵树,只是在操作的时候形成一棵树的结构。
下图是二叉Huffman树的具体执行过程:最终ans=33。

for(int i=1;i<n;i++){//n个数,操作(n-1)次
    int x=q.top();q.pop();
    int y=q.top();q.pop();
    q.push(x+y);
    ans+=x+y;
}

例1:P1090 合并果子

分析:
因为多多每次合并消耗的体力等于要合并的两堆果子的重量之和,所以最终消耗的体力就是每堆果子的重量\(\times\)合并的次数。这正符合Huffman树能解决的问题类型。
代码如下:

#include<bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> > q;//小根堆
int a[10010],ans;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        q.push(a[i]);
    }
    while(n!=1){//重复操作直到只剩下一个节点
        int x=q.top();q.pop();
        int y=q.top();q.pop();
        q.push(x+y);
        ans+=x+y;
        n--;
    }
    printf("%d",ans);
    return 0;
}

现在我们由二叉延伸到k叉Huffman树。此时将每次取出的2个数改为k个数。这时存在一个问题,在最后一次取值时,剩余的节点可能不足以取出k个。显然这样不是最优解,当我们任取Huffman树中一个深度最大的节点,并改为树根的子节点,此时\(\sum w_i \times l_i\)就会更小。
因此只有我们补加一些额外的空节点,并将这些空节点放置在最底层时才能保证贪心算法的正确性。
当\((n-1)mod(k-1)\neq 0\)时,我们还需要补充\((k-1)-(n-1)mod(k-1)\)个节点保证等式\((n-1)mod(k-1)=0\)成立。

例2:P2168 [NOI2015]荷马史诗

分析:
这道题构造的编码方式其实就是Huffman编码,我们把单词出现的次数作为Huffman树的叶节点的权值,然后求出k叉Huffman树即可。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct node{
    ll h,w;
    bool operator <(const node &other)const{
        return (w!=other.w)? w>other.w:h>other.h;
    }
};
priority_queue<node> q;
int n,k,sum;
ll t,ans;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&t);
        q.push((node){1,t});
    }
    if((n-1)%(k-1)) sum=(k-1)-(n-1)%(k-1);
    for(int i=1;i<=sum;i++) q.push((node){1,0});
    sum+=n;
    while(sum!=1){
        ll partw=0,maxh=0;
        for(int i=1;i<=k;i++){
            node now=q.top();q.pop();
            partw+=now.w;
            maxh=max(maxh,now.h);
        }
        ans+=partw;
        q.push((node){maxh+1,partw});
        sum-=(k-1);
    }
    printf("%lld\n%lld",ans,q.top().h-1);
    return 0;
}

原文地址:https://www.cnblogs.com/cyanigence-oi/p/11722050.html

时间: 2024-08-29 16:14:33

[算法]Huffman树(哈夫曼树)的相关文章

一步一步写算法(之哈夫曼树 下)

原文:一步一步写算法(之哈夫曼树 下) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面说到了哈夫曼树的创建,那下面一个重要的环节就是哈夫曼树的排序问题.但是由于排序的内容是数据结构,因此形式上说,我们需要采用通用数据排序算法,这在我之前的博客里面已经涉及到了(通用算法设计).所以,我们所要做的就是编写compare和swap两个函数.通用冒泡代码如下所示, void bubble_sort(void* array[], int le

一步一步写算法(之哈夫曼树 上)

原文:一步一步写算法(之哈夫曼树 上) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 在数据传输的过程当中,我们总是希望用尽可能少的带宽传输更多的数据,哈夫曼就是其中的一种较少带宽传输的方法.哈夫曼的基本思想不复杂,那就是对于出现频率高的数据用短字节表示,对于频率比较低得数据用长字节表示. 比如说,现在有4个数据需要传输,分别为A.B.C.D,所以一般来说,如果此时没有考虑四个数据出现的概率,那么我们完全可以这么分配,平均长度为2, /

Huffman tree(赫夫曼树、霍夫曼树、哈夫曼树、最优二叉树)

flyfish 2015-8-1 Huffman tree因为翻译不同所以有其他的名字 赫夫曼树.霍夫曼树.哈夫曼树 定义引用自严蔚敏<数据结构> 路径 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径. 路径长度 路径上的分支数目称作路径长度. 树的路径长度 树的路径长度就是从根节点到每一结点的路径长度之和. 结点的带权路径长度 结点的带权路径长度就是从该结点到根节点之间的路径长度与结点上权的乘积. 树的带权路径长度 树的带权路径长度就是树中所有叶子结点的带权路径长度之和,通常记做

【算法总结】哈夫曼树和哈夫曼编码

一.哈夫曼树 1. 哈夫曼树也称最优二叉树. 叶子节点的权值是对叶子节点赋予的一个有意义的数值量. 设二叉树具有 n 个带权值的叶子结点,从根节点到各个叶子结点的路径长度与相应叶子结点权值的乘积之和叫做二叉树的带权路径长度. 给定一组具有确定权值的叶子结点,可以构造处不同的二叉树,将其中带权路径长度最小的二叉树称为哈夫曼树. 2. 基本思想: 初始化:由给定的 n 个权值 $\left\{ \omega_{1},\omega_{2},\cdots ,\omega_{n}\right\}$构造 n

树-哈夫曼树(Huffman Tree)

概述 哈夫曼树:树的带权路径长度达到最小. 构造规则 1. 将w1.w2.-,wn看成是有n 棵树的森林(每棵树仅有一个结点): 2. 在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左.右子树,且新树的根结点权值为其左.右子树根结点权值之和: 3. 从森林中删除选取的两棵树,并将新树加入森林: 4. 重复(02).(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树. 基本操作 定义 1 权值 2 左孩子 3 右孩子 4 父节点 构造哈夫曼树(使用最小堆) 1 构造最小堆

5.2哈夫曼树——哈夫曼树与哈夫曼编码

#include <stdio.h> #include <stdlib.h> struct TreeNode{ int Weight; HuffmanTree Left, Right; }; HuffmanTree Huffman(MinHeap H) { int i; HuffmanTree T; for( i = 1; i < H->ESize; i++){ T = malloc(sizeof(struct TreeNode)); T->Left = Dele

经典算法题每日演练——第十三题 赫夫曼树

原文:经典算法题每日演练--第十三题 赫夫曼树 赫夫曼树又称最优二叉树,也就是带权路径最短的树,对于赫夫曼树,我想大家对它是非常的熟悉,也知道它的应用场景, 但是有没有自己亲手写过,这个我就不清楚了,不管以前写没写,这一篇我们来玩一把. 一:概念 赫夫曼树里面有几个概念,也是非常简单的,先来看下面的图: 1. 基础概念 <1>  节点的权: 节点中红色部分就是权,在实际应用中,我们用“字符”出现的次数作为权. <2>  路径长度:可以理解成该节点到根节点的层数,比如:“A”到根节点

哈夫曼树与哈夫曼编码

哈夫曼树与哈夫曼编码 术语: i)路径和路径长度 在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径. 路径中分支的数目称为路径长度.若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1. ii)结点的权及带权路径长度 若对树中的每个结点赋给一个有着某种含义的数值,则这个数值称为该结点的权. 结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积. iii)树的带权路径长度 树的带权路径长度:所有叶子结点的带权路径长度之和,记为WPL. 先了解一下

哈夫曼树(三)之 Java详解

前面分别通过C和C++实现了哈夫曼树,本章给出哈夫曼树的java版本. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 哈夫曼树的介绍 Huffman Tree,中文名是哈夫曼树或霍夫曼树,它是最优二叉树. 定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树. 这