Loading

哈夫曼树 Huffman

一些定义

PL

树的路径长度,即树根到每个叶节点的距离之和。

WPL

树的带权路径长度,即树根到每个叶节点的距离与每个叶结点权值的乘积之和。

哈夫曼树,也叫 Huffman 树,就是 WPL 最短的一种最优多叉树。

\[\]

哈夫曼树的构造

对于哈夫曼树的构造,我们以二叉哈夫曼树为例:

我们每次选择两棵根节点权值最小的树,将它们的根节点合并成一棵新树。

原来的两个根节点成为为新树的根节点的儿子。新树的根节点权值为合并的两个根节点权值之和。

假设我们要构造一棵初始有五个节点的二叉哈夫曼树,权值分别为 \(1,2,4,6,8\),则它们的构建过程如下:

(我腾讯文档没有 VIP 所以下载不了高清图片,只能将就看吧 qwq)

注意,哈夫曼树中允许多个点有相同的权值。

关于 K 叉哈夫曼树的构造

假设我们要构造一棵 \(k\) 叉哈夫曼树 \((k>2)\)

根据以上对哈夫曼树构造的原理,可以发现,这个构造方法是基于贪心的思想。每次取出最小的 \(k\) 个节点来合并成新的节点。

但是如果最后一步合并时,可选节点的数量不足 \(k\) 个,就会出现合并后的哈夫曼树根节点的儿子数小于 \(k\) 的情况。

此时它就不是一颗 WPL 最小的最优 \(k\) 叉树。因为此时我们随便取一个叶节点当做根节点的儿子,都可使 \(\sum (w_i\times l_i)\) 的值变小。

所以我们要满足 \((k-1)\mid (n-1)\) 这个条件时,才能使构造的 \(k\) 叉哈夫曼树达到最优。

而要满足这个条件,我们可以不断向图中加入权值为 \(0\) 的节点。显然 \(0\) 节点一定会先合并,这样可以使有权节点的深度降低。

\[\]

哈夫曼编码

哈夫曼编码的原则是:编码从叶子节点到根节点,译码从根节点到叶子节点。

但其实这句话对下面的内容并没有什么帮助。

我们还是以二叉哈夫曼树为例。

对于一棵二叉哈夫曼树,我们从根节点开始,对左子树编码 \(0\),对右子树编码 \(1\),直到遍历完整棵树。

此时我们再从根节点出发,将路径上的编码排列起来,一直到某个根节点,此时的编码串就是该根节点的哈夫曼编码。

举个例子,我们有一串电文 AMCADEDDMCCAD,需要将其翻译成 01 串,并使其尽量短。

我们统计每个字符的出现次数,可得到如下数据:

\(\begin{array}{ccc} E & M & C & A & D \\ \hline 1 & 2 & 3 & 3 & 4\\ \end{array}\)

我们将每种字符的出现次数作为节点的权值,可建造一棵如图所示的二叉哈夫曼树:

所以可得到各个字符的哈夫曼编码:

\(\begin{array}{c|cc} E&001\\ M&000\\ C&01\\ A&10\\ D&11\\ \end{array}\)

根据哈夫曼编码的定义,我们也可以看出,每种字符的编码长度之和为 PL,而每种字符的编码长度与其出现次数的乘积之和为 WPL。

此时的 PL 不一定是最短的,但 WPL 一定是最短的,只有这样才能使原串翻译后的 01 串最短。

当然上面只是说二叉哈夫曼树,其编码也只包含两种字符。假若要用 \(k\) 进制数来表示一个字符串,我们可以将其建成 \(k\) 叉哈夫曼树 \((k\ge 2)\)

对于代码的具体实现,结合下面的例题来说。

\[\]

例题

[NOI2015] 荷马史诗

有一个由 \(n\) 种字符组成的字串,给出这 \(n\) 种字符的出现次数,用 \(k\) 进制数来表示这个字符串。
求出这个 \(k\) 进制数的最短长度,并求出此时编码最长的字符的最短编码长度。

NOI 出模板题(

直接根据每种字符的出现次数建 \(k\) 叉哈夫曼树。

第一问就是 WPL 的长度,第二问就是在树中深度最大的字符的深度减一。

至于实现,我们运用优先队列存储每个节点的权值和深度,并按权值为第一关键字,深度为第二关键字从大到小排序。

首先将每个节点自己作为一棵树,放到优先队列中,并不断加入 \(0\) 节点直到满足条件为止。

每次取出 \(k\) 个节点来合并,同时统计 WPL。最后优先队列中只剩一个节点,输出统计结果与其深度减一即可。

struct node{
  int w,h;
  bool operator < (const node &a) const{
    return  a.w==w?h>a.h:w>a.w;
  }  
};

signed main(){
  n=read();k=read();priority_queue<node> q;
  for(int i=1,w;i<=n;i++) w=read(),q.push((node){w,1});
  while((q.size()-1)%(k-1)) q.push((node){0,1});
  while(q.size()>=k){
    int h=-1,w=0;
    for(int i=1;i<=k;i++){
      node t=q.top();q.pop();
      h=max(h,t.h);w+=t.w;
    }
    ans+=w;
    q.push((node){w,h+1});
  }
  printf("%lld\n%lld\n",ans,q.top().h-1);
  return 0;
}

\[\]

写在后面

没啦,啥都没啦。

posted @ 2021-06-19 11:22  KnightL  阅读(707)  评论(2编辑  收藏  举报