数据结构-----哈夫曼树和哈夫曼编码

news2024/11/14 20:50:55

 目录

前言

 哈夫曼树的实现

1.储存结构 

2.创建初始化哈夫曼树  

3.完整哈夫曼树创建

4.计算总权值

哈夫曼编码

完整代码


前言

        前面我们学习过了哈夫曼树和哈夫曼编码的基础知识(链接:哈夫曼树哈夫曼编码必知必会知识_Gretel Tade的博客-CSDN博客,如果没了解过哈夫曼树的话建议看看这个),那么这一节我们就学习怎么去通过代码的方式实现哈夫曼树和哈夫曼编码的功能,下面一起来看看。

 哈夫曼树的实现

1.储存结构 

哈夫曼树可以去通过顺序结构来储存,其物理形式是数组,逻辑结构是一棵二叉树(有点类似堆),当然也可以去通过链式结构来去实现,只是哈夫曼树一般没有增删改查的操作,也就是创建了哈夫曼树就进行直接使用,所以不需要去通过动态空间的方式来进行链式储存。其结构如下所示:

typedef char Datatype;
//节点
typedef struct {
	Datatype data;//储存数据
	int weight;	//权重
	int par, left, right;//指向节点
}Node,*Hufftree;

2.创建初始化哈夫曼树  

创建一个初始化的哈夫曼树,首先按照要求,顺序结构的哈夫曼树的第一个节点是为空的,不储存任何数据,从第二个位置开始储存数据。虽然要想创建一个n个节点的哈夫曼树其总节点为2n-1,但是由于第一个节点不储存任何数据,那就需要申请2n个节点空间。根据顺序结构的特性,我们可以把第1~n个数组节点作为叶子节点,然后第n+1~2n-1个节点作为叶子节点的父节点。代码如下:

//创建空哈夫曼树初始化
Hufftree Create_nulltree(int* w,Datatype* data,int n)
{
	Node* H = (Hufftree)malloc(sizeof(Node)*(2*n));//申请2n个空间
	if (!H)
	{
		printf("ERROR\n");
		exit(-1);
	}
	//初始化指向节点为-1
	for (int i = 1; i <= 2 * n - 1; i++) {
		H[i].par = H[i].left = H[i].right = -1;
	}
	//赋值节点数据
	for (int i = 1; i <=n; i++) {
		H[i].weight = w[i-1];
		H[i].data = data[i-1];
	}
	return H;
}

 比如给定节点{A,B,C,D},其权重分别为{7,5,2,4},那么创建的哈夫曼树结构应该是如下所示,但是创建了一个初始化的树,我们还需要把这些叶子节点的父节点权重给补填上去,那才能是一个完整的哈夫曼树。

3.完整哈夫曼树创建

我们都知道,哈夫曼树的每一个根节点权是等于两个叶子节点相加而来的,那么由此我们可以按照以下的方法来构造哈夫曼树

构造过程如下:

1、给定n个权值为{W1,W2,W3……Wn}的节点,构造n棵只有一个叶子节点的二叉树,从而得到一个二叉树集合F={T1,T2,T3……Tn}

2、在F中选取根结点权值最小和依次最小的两个二叉树作为左右子树,构造为一个新的二叉树,这个二叉树的根节点就是左右子树根节点权值之和

3、在集合F中删除作为左右子树的二叉树,并且把刚刚新建立的二叉树放入到集合F中去

4、重复2、3步骤,当F中只剩下一棵二叉树的时候,这个二叉树就是要建立的哈夫曼树,创建完成。

 

//构建完整哈夫曼树
void Create_hufftree(Hufftree H, int n) {
	assert(H);
	if (n <= 1)
		return;
	for (int i = n + 1; i <= 2 * n - 1; i++) {//对顺序表后面n+1~2n-1节点进行操作
		//初始化在这个过程中要用的数据
		int lmin = 32767;//第一小的权值
		int rmin = 32767;//第二小的权
		int lnode = -1;//左边最小值
		int rnode = -1;//右边最小值
		//对原来有了的节点和新创建的父节点进行挑选操作
		for (int j = 1; j <= i - 1; j++) {
			if (H[j].par == -1) {//筛选,父节点为空的节点处理
				if (H[j].weight < lmin) {
					rmin = lmin;
					rnode = lnode;
					lmin = H[j].weight;
					lnode = j;
				}
				else if (H[j].weight < rmin) {
					rmin = H[j].weight;
					rnode = j;
				}
			}
		}
		//构建树过程,此时lmin是当前最小权,rmin是第二小的权
		H[lnode].par = H[rnode].par = i;
		H[i].left = lnode;
		H[i].right = rnode;
		H[i].weight = H[lnode].weight + H[rnode].weight;
	}
}

4.计算总权值

要想计算总权值,按照哈夫曼树的权值计算方法,把节点权值乘上路径的长度,但是我们既然构建了哈夫曼树,我们只需要把所有除叶子节点的其他节点权值加起来就行了。比如上面图片那个哈夫曼树,其权值计算是:7+5*2+4*3+2*3=35。那我们把父节点加起来:6+11+18=35,看结果是一样的,具体的想想都很容易理解的,直接上代码:

//计数权值
int WPL(Hufftree H,int n) {
	int sum = 0;
	for (int i = n + 1; i <= 2 * n - 1; i++) {
		sum += H[i].weight;
	}
	return sum;
}

哈夫曼编码

有了哈夫曼树那就要给这个树进行编码,其顺序表有效节点是从1~n,所以我们要申请创建的字符串数组也应该是n+1个的,跟哈夫曼树节点一样数组的第0个位置是不用的。

代码书写思路:

 既然知道了哈夫曼编码的要求,也有了哈夫曼树,那我们怎么去写这个代码呢?我们可以这样子,从哈夫曼树的叶子节点开始向上遍历,用一个临时字符串储存遍历到的结果,这个老临时字符串开始的位置是从最后一位开始,判断如果此时这个节点那么就在这个临时字符串放入字符'0',反之放入'1',然后临时字符串储存位置向前移动一位,哈夫曼树向上移动一个节点……最后就可以完成这个节点的哈夫曼编码。代码如下:

//哈夫曼编码
char** Create_huffcode(Hufftree H, int n) {
	char** code = (char**)malloc(sizeof(char*) * (n+1));//创建哈夫曼编码储存字符串
	char* cd = (char*)malloc(sizeof(char) * n);//创建临时字符串空间储存字符串
	cd[n - 1] = '\0';
	int start, k, p;
	//方法是从子节点开始往上遍历,如果是左节点的话字符串cd就存入一个字符‘0’,反之放入‘1’,到最后的根节点,就遍历完成
	for (int i = 1; i <= n; i++) {//对每一个节点进行创建哈夫曼编码
		//初始化
		start = n - 1;//临时字符串从最后一个字符开始
		k = i;
		p = H[i].par;//标记子节点的父节点
		while (p!=-1) {//到达根节点结束
			start--;
			if (H[p].left == k)
				cd[start] = '0';
			else
				cd[start] = '1';
			//依次往上走
			k = p;
			p = H[k].par;
		}
		code[i] = (char*)malloc(sizeof(char) * (n-start));
		strcpy(code[i], &cd[start]);//复制拷贝
	}
	free(cd);//释放临时空间
	return code;
}

完整代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
typedef char Datatype;
//节点
typedef struct {
	Datatype data;//储存数据
	int weight;	//权重
	int par, left, right;//指向节点
}Node,*Hufftree;

//创建空哈夫曼树初始化
Hufftree Create_nulltree(int* w,Datatype* data,int n)
{
	Node* H = (Hufftree)malloc(sizeof(Node)*(2*n));//申请2n个空间
	if (!H)
	{
		printf("ERROR\n");
		exit(-1);
	}
	//初始化指向节点为-1
	for (int i = 1; i <= 2 * n - 1; i++) {
		H[i].par = H[i].left = H[i].right = -1;
	}
	//赋值节点数据
	for (int i = 1; i <=n; i++) {
		H[i].weight = w[i-1];
		H[i].data = data[i-1];
	}
	return H;
}

//构建完整哈夫曼树
void Create_hufftree(Hufftree H, int n) {
	assert(H);
	if (n <= 1)
		return;
	for (int i = n + 1; i <= 2 * n - 1; i++) {//对顺序表后面n+1~2n-1节点进行操作
		//初始化在这个过程中要用的数据
		int lmin = 32767;//第一小的权值
		int rmin = 32767;//第二小的权
		int lnode = -1;//左边最小值
		int rnode = -1;//右边最小值
		//对原来有了的节点和新创建的父节点进行挑选操作
		for (int j = 1; j <= i - 1; j++) {
			if (H[j].par == -1) {//筛选,父节点为空的节点处理
				if (H[j].weight < lmin) {
					rmin = lmin;
					rnode = lnode;
					lmin = H[j].weight;
					lnode = j;
				}
				else if (H[j].weight < rmin) {
					rmin = H[j].weight;
					rnode = j;
				}
			}
		}
		//构建树过程,此时lmin是当前最小权,rmin是第二小的权
		H[lnode].par = H[rnode].par = i;
		H[i].left = lnode;
		H[i].right = rnode;
		H[i].weight = H[lnode].weight + H[rnode].weight;
	}
}


//计算权值
int WPL(Hufftree H,int n) {
	int sum = 0;
	for (int i = n + 1; i <= 2 * n - 1; i++) {
		sum += H[i].weight;
	}
	return sum;
}

//哈夫曼编码
char** Create_huffcode(Hufftree H, int n) {
	char** code = (char**)malloc(sizeof(char*) * (n+1));//创建哈夫曼编码储存字符串
	char* cd = (char*)malloc(sizeof(char) * n);//创建临时字符串空间储存字符串
	cd[n - 1] = '\0';
	int start, k, p;
	//方法是从子节点开始往上遍历,如果是左节点的话字符串cd就存入一个字符‘0’,反之放入‘1’,到最后的根节点,就遍历完成
	for (int i = 1; i <= n; i++) {//对每一个节点进行创建哈夫曼编码
		//初始化
		start = n - 1;//临时字符串从最后一个字符开始
		k = i;
		p = H[i].par;//标记子节点的父节点
		while (p!=-1) {//到达根节点结束
			start--;
			if (H[p].left == k)
				cd[start] = '0';
			else
				cd[start] = '1';
			//依次往上走
			k = p;
			p = H[k].par;
		}
		code[i] = (char*)malloc(sizeof(char) * (n-start));
		strcpy(code[i], &cd[start]);//复制拷贝
	}
	free(cd);//释放临时空间
	return code;
}


int main() {
	int w[] = { 7,2,4,5 };
	Datatype data[] = { "ABCD" };
	int n = sizeof(w) / sizeof(int);
	Hufftree H = Create_nulltree(w, data,n);
	Create_hufftree(H,n);
	
	char** code = Create_huffcode(H, n);

	printf("总权值为:%d\n", WPL(H,n));
	for (int i = 1; i <= n; i++) {
		printf("%c:", H[i].data);
		printf("%s\n", code[i]);
	}
}

 以上就是本期的内容了,我们下次见!

分享一张壁纸: 

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

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

相关文章

内排序算法

排序算法是面试中常见的问题&#xff0c;不同算法的时间复杂度、稳定性和适用场景各不相同。按照数据量和存储方式可以将排序算法分为 内排序&#xff08;Internal Sorting&#xff09;和 外排序&#xff08;External Sorting&#xff09;。 内排序是指对所有待排序的数据都可…

wifi管理软件 WiFi Signal mac中文介绍

WiFi Signal mac是一款WiFi信号强度监测工具&#xff0c;它可以帮助用户实时监测WiFi信号的强度、频率、噪声等信息&#xff0c;并提供详细的图表和统计数据。 WiFi Signal可以自动扫描附近的WiFi网络&#xff0c;并显示它们的信号强度和频率。用户可以通过WiFi Signal来找到最…

Android斩首行动——应用层开发Framework必知必会

前言 相信做应用层业务开发的同学&#xff0c;都跟我一样&#xff0c;对Framework”深恶痛绝“。确实如此&#xff0c;如果平日里都在做应用层的开发&#xff0c;那么基本上我们很少会去碰Framework的知识。但生活所迫&#xff0c;面试总是逃不过这一关的&#xff0c;所以作为…

第二证券:A股公司首批三季报出炉 柏楚电子、平煤股份业绩一增一减

10月10日晚&#xff0c;柏楚电子、平煤股份拉开了A股公司三季报发表序幕。来自激光切开控制体系赛道的柏楚电子&#xff0c;前三季度营收、净利润均完结较大崎岖增加&#xff1b;焦煤龙头企业平煤股份&#xff0c;受煤价跌落连累成果&#xff0c;前三季度营收、净利润均有所下降…

Java架构师缓存性能优化

目录 1 缓存的负载策略2 缓存的序列化问题3 缓存命中率低4 缓存对数据库高并发访问5 缓存数据刷新的策略6 何时写缓存7 批量数据来更新缓存8 缓存数据过期的策略9 缓存数据如何恢复10 缓存数据如何迁移11 缓存冷启动和缓存预热1 缓存的负载策略 如果说我们在缓存架构设计当中啊…

优思学院|八大浪费深度剖析

在工作流程中消除浪费是精益思想的目标。在深入探讨八大浪费之前&#xff0c;了解浪费的定义至关重要。浪费是指工作流程中的任何行动或步骤&#xff0c;这些行动或步骤不为客户增加价值。换句话说&#xff0c;浪费是客户不愿意为其付费的任何过程。 最初的七大浪费&#xff0…

第83步 时间序列建模实战:Catboost回归建模

基于WIN10的64位系统演示 一、写在前面 这一期&#xff0c;我们介绍Catboost回归。 同样&#xff0c;这里使用这个数据&#xff1a; 《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal Syndr…

Nerf 学习笔记

Nerf 学习笔记 Step 1&#xff1a;相机 Rays 行进(ray marching)Step 2&#xff1a;收集查询点Step 3&#xff1a;将查询点投射到高维空间(位置编码)Step 4&#xff1a;神经网络推理和体渲染神经网络推理体渲染计算损失 Reference: 搞懂神经辐射场 Neural Radiance Fields (Ne…

如何在一个传统的html中,引入vueJs并使用vue复制组件?

如何在一个传统的html中&#xff0c;引入vueJs并使用vue复制组件&#xff1f; 1.1 引言1.2 背景1.3 解决方案1.3.1 解决方案一&#xff1a;直接使用clipboard(不推荐仅供参考学习)1.3.2 解决方案二&#xff1a;封装指令js库后使用 (推荐) 1.1 引言 这篇博文主要分享如何在一个…

Springboot给每个接口设置traceId,并添加到返回结果中

原理 slf4j有个MDC的类&#xff0c;是ThreadLocal的实现&#xff0c;保存在这里的变量会绑定到某个请求线程&#xff0c;于是在该请求的线程里的日志代码都可以使用设入的变量。 实现 一、引入依赖 这个是可选项&#xff0c;用于生成唯一uid&#xff0c;我人懒&#xff0c…

一文带你了解 Linux 的 Cache 与 Buffer

目录 前言一、Cache二、Buffer三、Linux 系统中的 Cache 与 Buffer总结 前言 内存的作用是什么&#xff1f;简单的理解&#xff0c;内存的存在是为了解决高速传输设备与低速传输设备之间数据传输速度不和谐而设立的中间层&#xff08;学过计算机网络的应该都知道&#xff0c;这…

【实战】kubeadmin安装kubernetes集群

文章目录 前言服务器介绍准备工作设置服务器静态ip修改host关闭防火墙和swap修改所需的内核参数 部署步骤安装containerd安装cri工具&#xff08;效果等同于docker&#xff09; 安装kubernetes集群安装网络插件flannel安装可视化面板kuboard&#xff08;可选&#xff09; 下期预…

42. QT中开发Android配置QFtp功能时遇到的编译问题

1. 说明 此问题仅适用在QT中开发Android程序时&#xff0c;需要适用QFtp功能的情况。一般情况下&#xff0c;如果开发的是Windows或者Linux系统下的程序&#xff0c;可能不会出现该问题。 2. 问题 【Android】在将QFtp的相关代码文件加入到项目中后&#xff0c;编译项目时会…

sql server判断两个集合字符串是否存在交集

sql server判断字符串A101;A102和字符串A102;A103是否存在交集 我们编写两个函数&#xff1a; 1&#xff09;函数fn_split将字符串拆分成集合 create function [dbo].[fn_split](inputstr varchar(8000), seprator varchar(10)) returns temp table (Result varchar(200)) a…

TCP/IP(七)TCP的连接管理(四)全连接

一 全连接队列 nginx listen 参数backlog的意义 nginx配置文件中listen后面的backlog配置 ① TCP全连接队列概念 全连接队列: 也称 accept 队列 ② 查看应用程序的 TCP 全连接队列大小 实验1&#xff1a; ss 命令查看 LISTEN状态下 Recv-Q/Send-Q 含义附加&#xff1a;…

2785323-77-3,MAL-Alkyne,双功能连接试剂Alkyne maleimide

炔烃马来酰亚胺&#xff0c;Alkyne maleimide,MAL-Alkyne是一种非常有用的双功能连接试剂&#xff0c;可以在生物分子中发挥重要的作用。它的马来酰亚胺基团可以与生物分子中的硫醇基团反应&#xff0c;形成共价键&#xff0c;从而将生物分子与炔烃连接起来。这种连接方式在生物…

React的类式组件和函数式组件之间有什么区别?

React 中的类组件和函数组件是两种不同的组件编写方式&#xff0c;它们之间有一些区别。 语法和写法&#xff1a;类组件是使用类的语法进行定义的&#xff0c;它继承自 React.Component 类&#xff0c;并且需要实现 render() 方法来返回组件的 JSX。函数组件是使用函数的语法进…

Linux|qtcreator编译可执行程序双击运行

qt GUI window移植到linux参见&#xff1a;VS|vs2017跨平台编译linux&&CConsole&&QtGUI 参考&#xff1a;QtCreator修改项目的生成目录 文章目录 双击.pro文件&#xff0c;点击configureproject构建项目切换到release模式下双击打开pro文件&#xff0c;修改依赖…

嵌入式Linux裸机开发(六)EPIT 定时器

系列文章目录 文章目录 系列文章目录前言介绍配置过程 前言 前面学的快崩溃了&#xff0c;这也太底层了&#xff0c;感觉学好至少得坚持一整年&#xff0c;我决定这节先把EPIT学了&#xff0c;下面把常见三种通信大概学一下&#xff0c;直接跳过其他的先学移植了&#xff0c;有…