二叉树的应用——哈夫曼树

news2024/11/23 15:54:43

哈夫曼树与哈夫曼编码

1.树的带权路径长

百分制成绩转五级制的算法流程图(A/B/C/D/E的人数分别为6/18/21/36/19)

带权路径长 = 路经长 x 权重

树的带权路经长:所有叶结点的带权路径长度之和。

例如:
(a)图中树的带权路经长(进行比较运算的总次数)为——6x1+18x2+21x3+36x4+19x4=325次
(c)图中树的带权路经长为——6x3+18x3+21x2+36x2+19x2=224次

由此可见,对于相同的叶结点,不同结构二叉树的带权路径长可能不同,在本例中,为了提高程序的运行效率,就需要采用带权路径长度最小的二叉树结构,接下来便引出哈夫曼树的概念。

 2.哈夫曼树及其构建方法

将m个值{w1,w2,...}作为m个叶结点的权重构造二叉树,则称树的带权路径长最小的二叉树为哈夫曼树,也称最优二叉树。   

算法步骤

(1)初始化 :构造m棵只有一个结点的二叉树,得到一个二叉树集合F={T1,T2...},Ti的叶结点(也是根结点)的权重为Wi;


(2)选取与合并:新建一个结点tmp,在F中选取根结点权重最小和次小的两棵二叉树Ti和Tj,分别作为tmp的左子树和右子树(顺序可以颠倒),构造一棵二叉树,结点tmp的权重为Ti和Tj的权重之和


(3)删除与加入:在F中删除Ti和Tj,并将以 tmp为根结点的新建二叉树加入F中;


(4)重复步骤(2)和步骤(3),直到F中只剩下一棵二叉树为止,此时,这棵二叉树就是哈夫曼树。                                                                                                   

 下图即为哈夫曼树

哈夫曼树的特点:

(1)权重越大的叶结点越靠近根结点,权重越小的叶结点越远离根结点;

(2)不存在度为1的结点,具有m个叶结点所构造的哈夫曼树公有2m-1个结点。 

#include<iostream>
#include<queue>
using namespace std;
typedef char datatype;
constexpr auto M = 30;;
//由于结点的总数不是太大,因此可以用二叉树左右链数组的表示法做适当的扩展来表示哈夫曼树的特点
//结点定义
typedef struct hutNode {
	datatype data;
	int w, idx; //idx为结点的下标,w表示权重
	int lc, rc;
	bool operator<(const hutNode& hn)const {
		return w > hn.w; //为了使hutNode类型的优先队列中权重较小的优先级较高
	}
}huTree[M<<1];

//由于每一步都要选择集合F中的最小权重结点和次小权重结点,
//且F中的结点是不断更新的,因此可以使用priority_queue
//创建哈夫曼树t:参数m为叶结点的数量,data和w分别为叶结点的数据信息和叶结点的权重
void huTree_create(huTree& t, const datatype data[], int w[], int m) {
	int i;
	hutNode hn1, hn2, hn3;
	priority_queue<hutNode>f;
	for (i = 1; i <= m; i++) {
		t[i].data = data[i - 1], t[i].w = w[i - 1], t[i].idx = i;
		t[i].lc = -1, t[i].rc = -1;
		f.push(t[i]);
	}
		while (f.size() > 1) { //当f只剩下一个元素时结束循环
			hn1 = f.top(), f.pop();
			hn2 = f.top(), f.pop();
			hn3.w = hn1.w + hn2.w, hn3.idx = i;
			hn3.lc = hn1.idx, hn3.rc = hn2.idx;

			f.push(hn3);
			t[i++] = hn3;
		}
		t[0] = f.top(), t[0].idx = 0; //将最终哈夫曼树的根结点的编号设置为0	
}

int main() {
	const char* data = "EDCBA";
	int w[] = { 6,18,21,36,19 };
	huTree t;
	huTree_create(t, data, w, 5);
}

 3.哈夫曼编码与解码

哈夫曼编码

哈夫曼树就是哈夫曼在研究变长编码时发明的用于产生满足前缀编码特性的最优变长编码的二叉树。由哈夫曼树所生成的字符集的编码称为哈夫曼编码。

哈夫曼编码依据每个字符在文本中出现的频率(或概率)来构造字符的编码,且用这种编码方法对文本进行编码所得到的二进制编码长度最短,也称为最优编码。

算法——哈夫曼编码的生成方法


功能:对于文本 txt,对其进行编码,使得总文本的总编码长度最短。


算法步骤如下:
(1)统计文本 txt 中各个字符出现的频率(或概率),并将它们作为叶结点的权重,利用
上述算法构建哈夫曼树,此时哈夫曼树的叶结点代表一个字符;
(2)将哈夫曼树的每个左分支标记为0,右分支标记为 1;
(3)将从根结点到每个叶结点的路径上的各分支的标记连接起来构成一个二进制串,该二进制串即为叶结点所对应字符的编码;
(4)依次将文本中字符转化为编码,并连接为一个字符串,即为该文本的编码。

应用实例: 

哈夫曼解码 

算法:哈夫曼解码

功能:对编码code进行解码,解码结果存放到ret中

步骤:

(1)从哈夫曼树的根结点开始,依次考虑code的每一个字符

(2)如果当前字符为0,则进入左子树,否则进入右子树,并考虑code的下一个字符;

(3)重复步骤(2),当达到叶结点是,将叶结点所对应的字符添加到ret的后面,考虑下一个字符,回到第2步;

(4)当code的所有字符都处理后,解码结束。

注意:对每一个字符的解码都是从哈夫曼树t的根结点开始的,因此当完成一个字符解码之后,需要回到t的根结点。

综上完整代码:

#include<iostream>
#include<queue>
#include<map>
using namespace std;
typedef char datatype;
constexpr auto M = 30;;
//由于结点的总数不是太大,因此可以用二叉树左右链数组的表示法做适当的扩展来表示哈夫曼树的特点
//结点定义
typedef struct hutNode {
	datatype data;
	int w, idx; //idx为结点的下标,w表示权重
	int lc, rc;
	bool operator<(const hutNode& hn)const {
		return w > hn.w; //为了使hutNode类型的优先队列中权重较小的优先级较高
	}
}huTree[M << 1];

//由于每一步都要选择集合F中的最小权重结点和次小权重结点,
//且F中的结点是不断更新的,因此可以使用priority_queue
//创建哈夫曼树t:参数m为叶结点的数量,data和w分别为叶结点的数据信息和叶结点的权重
void huTree_create(huTree& t, const datatype data[], int w[], int m) {
	int i;
	hutNode hn1, hn2, hn3;
	//记住此时的初始化内容,哈夫曼编码用到,无子树为-1,有设置为索引
	priority_queue<hutNode>f; 
	for (i = 1; i <= m; i++) {
		t[i].data = data[i - 1], t[i].w = w[i - 1], t[i].idx = i;
		t[i].lc = -1, t[i].rc = -1;
		f.push(t[i]);
	}
	while (f.size() > 1) { //当f只剩下一个元素时结束循环
		hn1 = f.top(), f.pop();
		hn2 = f.top(), f.pop();
		hn3.w = hn1.w + hn2.w, hn3.idx = i;
		hn3.lc = hn1.idx, hn3.rc = hn2.idx;

		f.push(hn3);
		t[i++] = hn3;
	}
	t[0] = f.top(), t[0].idx = 0; //将最终哈夫曼树的根结点的编号设置为0	
}

//生成一段文本的哈夫曼树编码
//利用哈夫曼树t对每个叶结点进行编码,编码存放在参数code中
/*
	采用先根遍历算法获得每一个业界点的编码,注意弄清回溯过程
*/
void hfTree_coding(huTree t, int cur, map<char, string>& code) {
	static string tmp;
	if (t[cur].lc == -1) {
		code[t[cur].data] = tmp; ///达到叶结点,保存编码
		return;
	}
	tmp += '0';
	hfTree_coding(t, t[cur].lc, code); //左分支
	tmp.erase(tmp.length() - 1);//回溯时将前面所添加的0删除
	tmp += '1';
	hfTree_coding(t, t[cur].rc, code);//右分支
	tmp.erase(tmp.length() - 1);//回溯时将前面所添加的1删除
}

//求txt的编码,并作为函数的返回值
/*
	首先统计文本txt中出现的字符及其次数,然后产生哈夫曼树,
	由于解码用到哈夫曼树,所以参数设置为引用类型;
	然后利用hfTree_coding产生每个字符的编码;
	最后利用每个字符的编码得到文本txt的编码
*/
string coding(huTree& t, string txt) {
	//m为txt中不同字符的数量,w为txt中每个字符出现的次数
	int w[M], m = 0, len = txt.length(), i;
	char data[M];
	map<char, int>ma;
	for (int i = 0; i < len; i++)
		ma [txt[i]]++;//txt中出现的字符
	for (map<char, int>::iterator it = ma.begin(); it != ma.end(); it++)
		data[m++] = it->first, w[m++] = it->second;//转存
	huTree_create(t, data, w, m);//创建哈夫曼树
	map<char, string>code;
	hfTree_coding(t, 0, code);//获得每个字符出现的次数
	string ret; //存放编码结果
	for (int i = 0; i < len; i++) {//存放txt的编码
		ret += code[txt[i]];
	}
	return ret;
}

//哈夫曼解码:解码过程中所得到的每一个字符都对应于哈夫曼树的根结点与某个叶结点之间的一条路径
//利用哈夫曼树t对编码code进行解码,t为编码时所创建的哈夫曼树
string decoding(huTree t, string code) {
	int i, len = code.length();
	string ret;
	hutNode cur = t[0]; //从哈夫曼树的根结点出发
	for (int i = 0; i < len; i++) {
		if (code[i] == '0') //当前编码为‘0’,进入左子树
			cur = t[cur.lc];
		else
			cur = t[cur.rc];
		if (cur.lc == -1) {
			ret += cur.data;
			cur = t[0];
		}
	}
	return ret;
}

int main() {
	string s = "AABCD";
	huTree t;
	string s1 = coding(t, s);
	cout << s1<<endl;
	string s2 = decoding(t, s1);
	cout << s2<<endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/168639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[ 攻防演练演示篇 ] 利用谷歌 0day 漏洞上线靶机

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

橘子学kafka之基础命令使用

本系列主要开始处理关于kafka的一些技术知识点&#xff0c;尽量会以代码和实际命令为主要表达形式来做表现。 本文主要是关于如何在客户端使用命令做一个描述&#xff0c;其实我本来不想写的&#xff0c;但是今天在公司有同事居然不会&#xff0c;所以我觉得还是描述一下。而且…

贪心算法合集

95 分糖果问题 思路非常简单&#xff0c;和题解一模一样&#xff1a; 用数组存每个人对应的糖果数量&#xff0c;初始为1 从左到右遍历&#xff0c;如果比左边的大&#xff0c;1再从右到左遍历&#xff0c;如果比右边的大&#xff0c;1 import java.util.*;public class Solu…

录屏大师电脑版推荐(一键录制声画同步的视频)

很多小伙伴使用电脑多年&#xff0c;却不知道电脑有录屏功能。想要对电脑屏幕进行录制&#xff0c;只需在电脑上安装一个录屏大师。那有没有录屏大师电脑版推荐呢&#xff1f;在试用了多款电脑录屏大师之后&#xff0c;小编今天给大家推荐一款可以一键录制声画同步视频的录屏大…

使用Python为二年级的学生批量生成数学题

文章目录一.使用Python为二年级的学生批量生成数学题1.1 背景二.解决思路及其代码三.排版及其打印四.本文源码一.使用Python为二年级的学生批量生成数学题 1.1 背景 我妹妹今年上二年级&#xff0c;她的老师今天给他们布置了一项作业&#xff1a; 从今天起到开学&#xff0c;…

ENSP的AR40问题解决

AR40以及其他相关问题都可以参考此方法&#xff0c;都已经可以正常使用 现在有enspAR40问题的同学有救了&#xff0c;ensp的AR40问题困扰了我很长时间&#xff0c;根据官方的问题解决文档没有解决&#xff0c;反正就是之前的所有方法都没有用&#xff0c;也不是都没有用&#x…

初读《编程之美》就想秀一下,结果还翻车了

文章目录 一、前言 二、我的思路 三、Code 四、翻车现场 五、后续问题 一、前言 ———如何写一个短小的程序&#xff0c;让 Windows 的任务管理器显示CPU的占用率为50%? 这道有趣的面试题我是这两天从《编程之美》电子版中看到的&#xff0c;看意思就是邹老师在微软对一…

入门postgre sql(PG的下载和安装,包括普通用户源码构建的安装方式)

目录PG的下载安装1、Windows 上安装2、Linux上安装有root权限的安装无root权限的安装PG的下载安装 点击这里&#xff0c;了解pg 1、Windows 上安装 (1)下载安装 访问官网下载地址 https://www.enterprisedb.com/downloads/postgres-postgresql-downloads 下载最新发布的Po…

3.kafka-3.生产者,消费者

文章目录1.个性化配置&#xff0c;增加吞吐量2.发送事务消息3.消费组手动提交offset指定offset位置进行消费指定时间消费当新增消费者&#xff0c;或者消费组时&#xff0c;如何消费漏消息和重复消息如何解决消费解压问题1.个性化配置&#xff0c;增加吞吐量 private static vo…

使用 .NET 7、Blazor 和 .NET MAUI 构建你自己的 Podcast App

.NET Podcast App 首次在 .NET Conf 2021上推出&#xff0c;最近进行了更新以在 .NET Conf 2022 keynote 中突出显示 .NET 7 中的新功能。该 Podcast App 已准备好使用展示 .NET&#xff0c;ASP.NET Core&#xff0c;Blazor&#xff0c;.NET MAUI&#xff0c;Azure Container A…

Android 蓝牙开发——概述(一)

一、蓝牙简介 蓝牙技术是一种无线数据和语音通信开放的全球规范&#xff0c;它是基于低成本的近距离无线连接&#xff0c;为固定和移动设备建立通信环境的一种特殊的近距离无线技术连接。 其中将1.x~3.0之间的版本称之为经典蓝牙&#xff0c;4.x开始的蓝牙称之为低功耗蓝牙&…

Memcache学习总结

这里写自定义目录标题介绍一致性哈希寻找节点一致性哈希介绍内存管理slab结构寻找存储chunkChunk中存储的Item数据结构grow factor 调优回收删除一些特性介绍 基于内置内存Key-Value形式存储数据(字符串、对象)集群服务器是通过数组链表方式存储K-V数据<分布式>基于哈希…

编程语言那么多,我为什么推荐你学Java?

Java一直都是稳居排行榜第一的语言&#xff0c;在未来10年Java都会是最热门的语言之一&#xff0c;因为Java技术具有卓越的通用性、高效性、安全性和平台移植性&#xff0c;它可以跨平台的应用到不同的领域&#xff0c;工作需求足够大。 为什么选择学习Java编程语言&#xff1…

更具科技感的中塔机箱,模块设计兼容性强,鑫谷昆仑御风机箱上手

大家装机的时候应该都接触过鑫谷的机箱和散热器外设&#xff0c;作为一家有年头的外设品牌&#xff0c;这两年鑫谷推陈出新&#xff0c;像是在电源方面&#xff0c;就有不少很受欢迎的产品&#xff0c;像是昆仑系列等&#xff0c;前端鑫谷在昆仑系列中带来了一款设计新颖的机箱…

琥珀酰亚胺-双硫键-琥珀酰亚胺NHS-SS-NHS双端活性酯二硫键交联剂

名称:NHS-SS-NHS 中文名称:活性酯-双硫键-活性酯 琥珀酰亚胺-双硫键-琥珀酰亚胺 分子式 :C14H16N2O10S2 分子量 :436.41 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 用 途&#xff1a;仅供科研实验使用&#xff0c;不用于诊治 外观: 固体或粘性液体&am…

VMwareWorkstationPro16的下载与安装,以及vm账号注册的问题

VMwareWorkstationPro16的下载与安装&#xff0c;以及vm账号注册的问题查看虚拟化支持是否开启vm的安装vm账号注册的常见问题VM 16的安装步骤查看虚拟化支持是否开启 可以从任务管理器中的性能去查看CPU是否开启虚拟化支持 vm的安装 访问 vm 的官网: https://www.vmware.co…

I2C_Adapter驱动框架讲解与编写

I2C_Adapter驱动框架讲解与编写 文章目录I2C_Adapter驱动框架讲解与编写参考资料&#xff1a;一、 回顾1.1 2C驱动程序的层次1.2 I2C总线-设备-驱动模型二、 I2C_Adapter驱动框架2.1 核心的结构体1. i2c_adapter2. i2c_algorithm2.2 驱动程序框架1. 所涉及的函数2. i2c_algorit…

lq-递归

1、递归实现指数型枚举从 1∼n 这 n个整数中随机选取任意多个&#xff0c;输出所有可能的选择方案。输入格式输入一个整数 n。输出格式每行输出一种方案。同一行内的数必须升序排列&#xff0c;相邻两个数用恰好 1个空格隔开。对于没有选任何数的方案&#xff0c;输出空行。本题…

AStar(A*)算法核心思想( for unity)

AStar算法算法思想举例理解核心代码A* 算法&#xff0c;A* (A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法&#xff0c;也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近&#xff0c;最终搜索速度越快。 注意:AStar的类应该作为一种单例类只…

软考初级哪个好考

其实软考初级的实用性差不多。只是看自身怎么看&#xff0c;考哪一科对你来说&#xff0c;产生的意义更大&#xff0c;对自己以后的发展前景帮助大&#xff0c;那你就选择哪一科就行。 软考初级科目有&#xff1a;程序员、网络管理员、信息处理技术员、信息系统运行管理员、网…