哈夫曼树学习笔记

1|0定义:

  • 1.二叉哈夫曼树:对于一个数列,构建一棵树上带权路径之和最小的二叉树(当然可以k叉)
  • 2.树上带权路径:每个叶子节点到根节点的路径上所有节点的点权w和到跟的路径长度dis的乘积之和
    简单来说,哈夫曼树满足wi×disi最小

2|0基本构造方法:

前置知识(没做过也可以,但至少要会优先队列):合并果子
一点没错,就是合并果子。你考虑这么一件事,我们想要的就是权值越大的店越靠近根节点,这样wi越大disi越小显然更优。那每次找两个最小权值合并就行了。

具体的步骤如下:

  • 1.将n个元素创建n个节点并视为n棵树,权值wi即为元素ai
  • 2.选择权值最小的两个根节点x,y,创建一个虚拟节点,并将左右子树设为x,y,权值为x+y(相当于把这两棵树合并了)
  • 3.重复步骤2直到成为一棵树

拿一个序列过来演示一下:1 9 8 0 4

image
01合并:
image
继续合并:
image
image
image
完成!

3|0哈夫曼编码:

哈夫曼树通常和哈夫曼编码一起使用,什么是哈夫曼编码呢?

我们截取小明一天给他同学发的短信:

  • 1.我今天又爆零了
  • 2.我中午吃的面
  • 3.今天考试爆零的好多
    这些消息在二进制下就是一堆01串,而且在正常编码的情况下每个汉字占的字节都一样多。我们知道传输信息要消耗能量,那么能不能让传输的字符串短一点呢。

一种思路是:让使用更为频繁的汉字编码尽可能的短,比如上文的三句话中,“我”这个字出现次数多,编成0,“面”这个字不怎么常用,编成10101100

可还有一个问题,假设“我”是0,“今”是1,“天”是10,那么10既可以表示“天”,同时也是“我今”。

怎么解决这个问题呢?我们观察上面构造的哈夫曼树,发现每个原节点都是叶子节点,这是显然的,那么就不可能出现一个节点的编码是另一个的前缀。我们只需要把每个字的使用次数当作权值建立哈夫曼树,从根节点开始,向左在字符串后加一位0向右走加一位1,每个叶子节点的哈夫曼编码就构造完成了。

ps:

4|0例题:

4|11.HDU - 1053 Entropy

链接
第一个答案是正常编码的长度,众所周知一个字符长度为8个比特,直接输出8×长度

第二个答案是哈弗曼编码的长度,你不用真的建树(建了当然也可以),直接用合并果子就行。但要特判只有一种字符的情况(根本不合并)

第三个答案是前两个的比值

4|2CODE:

#include<iostream> #include<queue> #define int long long using namespace std; int ans1,ans2; double ans3; string s; priority_queue<int, vector<int> , greater<int> > q; int times[100]; signed main(){ while(cin>>s && s!="END"){ ans2=0; ans1=s.size()*8; for(int i=0;i<=26;i++) times[i]=0; for(int i=0;i<s.size();i++){ if(s[i]!='_') times[s[i]-'A']++; else times[26]++; } for(int i=0;i<=26;i++){ if(times[i]) q.push(times[i]); } while(q.size()>1){ int aa=q.top(); q.pop(); int bb=q.top(); q.pop(); q.push(aa+bb); ans2+=(aa+bb); } if(!ans2) ans2=q.top(); ans3=(1.0*ans1)/(1.0*ans2); cout<<ans1<<" "<<ans2<<" "; printf("%.1lf",ans3); cout<<endl; while(q.size()) q.pop(); } return 0; }

4|32.P2168 [NOI2015] 荷马史诗

链接
一眼哈夫曼树,可是是k叉。
其他都没有区别,第一问就是最大深度减一。值得注意的是因为最后一次合并不一定有k个根节点,所以可能出现这种情况(k=3):
image
显然3,4号节点应该放上去。

于是我们一开始不停插入权值为0的节点直到最后一次合并有k个根节点,因为其他的权值都比0大,所以0会在最下面,其他的节点自然就上去了。

你可能会问怎么知道插入几个0,很简单,第一次是n个原节点合并,后面每次是n1个原节点和1个虚拟节点合并。所以最后的总结点数1要整除(k1)

4|4CODE:

#include<bits/stdc++.h> #define int long long using namespace std; int ans=0; int n,k; struct node{ int w,deep; bool operator<(const node &a)const{ if(w==a.w) return deep>a.deep; return w>a.w; } }; priority_queue<node> q; signed main(){ cin>>n>>k; for(int i=1;i<=n;i++){ int siz; cin>>siz; node pus; pus.deep=1; pus.w=siz; q.push(pus); } while((q.size()-1)%(k-1)!=0){ node pus; pus.deep=1; pus.w=0; q.push(pus); } while(q.size()>=k){ int h=0,w=0; for(int i=1;i<=k;++i){ node t=q.top(); q.pop(); h=max(h,t.deep); w+=t.w; } ans+=w; node pus; pus.deep=h+1; pus.w=w; q.push(pus); } cout<<ans<<endl<<q.top().deep-1; return 0; }

__EOF__

本文作者星河倒注
本文链接https://www.cnblogs.com/wangwenhan/p/17654467.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   星河倒注  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示