头像

欢迎来到我的博客

分享题解与总结

哈夫曼编码(1)

注意

需要先看完这一视频哈夫曼树和哈夫曼编码, 看完秒懂!_哔哩哔哩_bilibili

背景

给一段文本编码,要求:解码无歧义;最小化总编码长度

核心

贪心+优先队列

性质

  1. 所有待编码的字符都放在哈夫曼树的叶子上
  2. 出现频率低的字符先编码,出现频率高的字符后编码
  3. 出现频率低的字符,所处层次越深(编码较长)
  4. 出现频率高的字符,所处层次越低(编码较短)
  5. \(文本总编码长度=∑length(i)*w[i]\)(length(i):字符i的编码长度,w[i]:字符i出现的频率)
  6. k叉哈夫曼树,要求最长编码尽可能短(追求平衡),需要补虚拟结点

练习题

题目

给定n个字符及其出现的频率,构建哈夫曼树(左0右1),要求:

  1. 输出哈夫曼树的层序遍历结果(输出每个节点的权值)
  2. 输出每个字符的哈夫曼编码(按照输入顺序输出)

输入格式

一个数n,表示有n个字符

接下来n行,每行包括一个字符及其出现的频率

输出格式

  1. 层序遍历结果
  2. 每个字符的哈夫曼编码

输入样例

4
a 5
b 9
c 12
d 13

输出样例

39 14 25 5 9 12 13
a:00
b:01
c:10
d:11

样例解释

屏幕截图 2026-03-08 190810

代码结构

结点存储

//二叉
struct node{int wi,idx;};
//k叉
struct node{int wi,hi,idx;};

wi:权重

idx:节点编号

优先队列

生存期:优先队列在哈夫曼树构建完后,就几乎消失

struct cmp{
	bool operator()(const node& a,const node& b) const {
		return a.wi>b.wi;
	}
};
priority_queue<node,vector<node>,cmp> pq;

小根堆:权值小的优先

其余存储结构

生存期:长期存在,利于最终输出编码/总长度

//权值 左孩子 右孩子
int w[N],lch[N],rch[N];
//记录字符对应的编码
map<char,string> codemap;

输入

cin>>n;
for(int i=1;i<=n;++i){
    cin>>ch[i]>>w[i];
    pq.push({w[i],i});
    lch[i]=0; rch[i]=0; 
}

补虚拟节点

//只针对k叉
need=(k-1-(n-1)%(k-1))%(k-1);
cnt=n;
for(int i=1;i<=need;++i){
    w[++cnt]=0;
    ch[cnt]='\0';
    h[cnt]=0;
    pq.push({w[cnt],0,cnt});
}

构建哈夫曼树

//二叉
cnt=n;
while(pq.size()>1){
    lft=pq.top(); pq.pop();
    rgt=pq.top(); pq.pop();
    cnt++;
    w[cnt]=lft.wi+rgt.wi; lch[cnt]=lft.idx; rch[cnt]=rgt.idx;
    pq.push({w[cnt],cnt});
}
//k叉
while(pq.size()>1){
    w[++cnt]=0;

    for(int i=1;i<=k;++i){
        auto head=pq.top(); pq.pop();
        w[cnt]+=head.wi;
        h[cnt]=max(h[cnt],head.hi);
        sons[cnt].push_back(head.idx);
    }
    h[cnt]++;
    pq.push({w[cnt],h[cnt],cnt});
}
root=pq.top();

层序遍历BFS

核心:从当前结点扩展到子节点

注意:在取出head的时候输出w[head]

//二叉
void levelOrder(int r)
{
	int head,lchild,rchild;
	queue<int> q;
	q.push(r);

	while(!q.empty()){
		head=q.front(); q.pop();
		cout<<w[head]<<" ";
		
		lchild=lch[head]; rchild=rch[head];
		if(lchild){//左孩子
			q.push(lchild);
		}
		if(rchild){//右孩子
			q.push(rchild);
		}
	}
	cout<<"\n";
}

//k叉
void levelOrder(int r)//层序遍历BFS 
{
	queue<int> q;
	q.push(r);
	
	while(!q.empty()){
		auto head=q.front(); q.pop();
		cout<<w[head]<<" ";
		
		for(auto child:sons[head]){//各个孩子
			q.push(child);
		}
	}
	cout<<"\n";
}

编码获取DFS:

从根节点开始DFS,直到遇到树叶(结点编号<=n)

注意:只有一个结点时,path=“”,需要手动改为path="0"

//二叉
void getCode(int v,string path)
{
	if(v<=n){//原始字符 叶子结点 
		if(path.size()==0) path="0";  
		codemap[ch[v]]=path;
		return ;
	}
	
	if(lch[v]) getCode(lch[v],path+"0");//左孩子
	if(rch[v]) getCode(rch[v],path+"1");//右孩子
} 
//k叉
void getCode(int v,string path)
{
	int i=0;
	char tmp;
	if(v<=n){
		if(path.size()==0) path="0";
		codemap[ch[v]]=path;
		return ;
	}
	for(auto child:sons[v]){//各个孩子
		tmp='0'+i;
		getCode(child,path+tmp);
		i++;
	}
} 

调用与输出

levelOrder(root.idx);
getCode(root.idx,"");
for(int i=1;i<=n;++i){
    cout<<ch[i]<<":"<<codemap[ch[i]]<<"\n";
}
posted @ 2026-03-08 19:25  king_steph1209  阅读(21)  评论(0)    收藏  举报