【哈夫曼树】

2023-11-11

前言

哈夫曼树又称最优二叉树,可以对带权节点进行编码并且保证每个数据的编码都不会是其他数据的前缀,保证了编码的唯一性,因此,哈夫曼编码又称为前缀码。
(注意:哈夫曼编码方式是可以根据要求改变的,所以按照方案1编码的数据需要按照方案1的编码要求进行解释,否则可能得不到正确的结果)
由于哈夫曼树中只有没有孩子的叶子节点和有两个孩子的二叉节点,根据n0=n2+1, 得到总节点数N:N=2*n0+1
我们可以创建哈夫曼树编码表,前n0个为待编码节点,后N-n0个为parent节点,由于是用数组存放,并且它们的位置不会改变,孩子和双亲指针都用下标表示即可。
这里我们规定:左孩子的权值要小于右孩子,并且左孩子编码为0,右孩子编码为1,之后就是连接叶子节点并且找到对应的编码即可。


1.哈夫曼树结构


#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct HTNode
{
	char data;         // 数据
	int weight;     // 权值
	int lchild;			 // 左孩子下标
	int rchild;			//右孩子下标
	int parent;			//父亲节点下标,设置父节点是为了从叶子结点向上查找获取哈夫曼编码
}HTNode, * PHT;

typedef char** PPCHAR;

2.初始化

我们这里将数据放到数组中并且在后序操作中不改变数据的位置,可以方便之后的操作。

void InitHuffmanNode(PHT hf, int con)  // con为待编码节点个数
{
	for (int i = 0; i < con; i++)
	{
		printf("请输入节点的数据和权值:");
		scanf("%c %d", &hf[i].data, &hf[i].weight);            // 前con个节点为待编码节点
		getchar();
		hf[i].lchild = hf[i].rchild = hf[i].parent = -1;  //  初始化为空,此时每个节点都是独立的
	}
	//  后面的节点可以在连接时赋值
}

3.构造哈夫曼树

每次选出两个 权值最小 并且 没有父节点 的节点连接。

void CreateHuffman(PHT& hf, int con)
{
	for (int i = 0; i < con - 1; i++)  // 连接次数
	{       
		int minsub1 = 0;             // 第一个节点的下标
		int minweight1 = INT_MAX;     // 第一个节点的权值,找到比它小的就交换
		int minsub2 = 0;
		int minweight2 = INT_MAX;

		for (int j = 0; j < con + i; j++)  // 选取节点时的范围,由于会不断设置父节点,所以每连接一次节点总数多一个
		{
			if (hf[j].parent == -1 && hf[j].weight < minweight1)  // 没有父节点,权值小
			{
				minweight1 = hf[j].weight;
				minsub1 = j;
			}
		}

		for (int j = 0; j < con + i; j++)  // 选取第二个节点
		{
			if (hf[j].parent == -1 && j != minsub1 && hf[j].weight < minweight2) // 没有父节点,和第一个不重复,权值小
			{
				minweight2 = hf[j].weight;
				minsub2 = j;
			}
		}

		// 连接
		hf[con + i].weight = hf[minsub1].weight + hf[minsub2].weight;  // 赋权值
		hf[con + i].lchild = minsub1;    // 连接两个子节点
		hf[con + i].rchild = minsub2;
		hf[con + i].parent = -1;

		hf[minsub1].parent = con + i;     // 为子节点赋值父节点
		hf[minsub2].parent = con + i;
	}
}


4. 获取Huffman编码

我们要找到各个节点的哈夫曼编码,就需要先找到该节点,然后从该节点往上走到根节点,记录下走过的路径,就是它的哈夫曼编码。


void GetHuffmanCode(PHT hf, PPCHAR str, int* weight, int con)
{

	char* tmp = (char*)malloc(con);    // 记录每个叶子结点的编码
	if (tmp == NULL)
		exit(-1);

	tmp[con - 1] = '\0';         //   字符串结束
	// 叶子结点位置就是前con个
	for (int i = 0; i < con; i++)
	{
		int t = con - 2;        // tmp有效下标
		int child = i;              //   从叶子结点向上遍历
		int parent = hf[i].parent;
		weight[i] = hf[i].weight;  // 权值
		int len = 0;     // 记录经过的路径个数
		while (parent != -1)
		{
			len++;
			if (hf[parent].lchild == child)    //   左孩子编码为‘0’,右孩子编码为‘1’
				tmp[t--] = '0';
			else
				tmp[t--] = '1';

			child = parent;        // 孩子和父亲都向上走,更新下标
			parent = hf[parent].parent;
		}
		weight[i] *= len;                     //    该节点的WPL

		str[i] = (char*)malloc(len + 1);  //  编码和 '\0' 总数
		if (str[i] == NULL)
			exit(-1);

		strcpy(str[i], tmp + t + 1);         //  将有效字符拷贝过去
	}

	free(tmp);
}

整体代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct HTNode
{
	char data;         // 数据
	int weight;     // 权值
	int lchild;			 // 左孩子下标
	int rchild;			//右孩子下标
	int parent;			//父亲节点下标,设置父节点是为了从叶子结点向上查找获取哈夫曼编码
}HTNode, * PHT;

typedef char** PPCHAR;

//  建立哈夫曼编码表
void InitHuffmanNode(PHT hf, int con)
{
	for (int i = 0; i < con; i++)
	{
		printf("请输入节点的数据和权值:");
		scanf("%c %d", &hf[i].data, &hf[i].weight);            // 前con个节点为待编码节点
		getchar();
		hf[i].lchild = hf[i].rchild = hf[i].parent = -1;  //  初始化为空,此时每个节点都是独立的
	}
	//  后面的节点可以在连接时赋值
}

 // 每次选出两个 权值最小 并且 没有父节点  的节点连接
void CreateHuffman(PHT hf, int con)
{
	for (int i = 0; i < con - 1; i++)  // 连接次数
	{       
		int minsub1 = 0;             // 第一个节点的下标
		int minweight1 = INT_MAX;     // 第一个节点的权值,找到比它小的就交换
		int minsub2 = 0;
		int minweight2 = INT_MAX;

		for (int j = 0; j < con + i; j++)  // 选取节点时的范围,由于会不断设置父节点,所以每连接一次节点总数多一个
		{
			if (hf[j].parent == -1 && hf[j].weight < minweight1)  // 没有父节点,权值小
			{
				minweight1 = hf[j].weight;
				minsub1 = j;
			}
		}

		for (int j = 0; j < con + i; j++)  // 选取第二个节点
		{
			if (hf[j].parent == -1 && j != minsub1 && hf[j].weight < minweight2) // 没有父节点,和第一个不重复,权值小
			{
				minweight2 = hf[j].weight;
				minsub2 = j;
			}
		}

		// 连接
		hf[con + i].weight = hf[minsub1].weight + hf[minsub2].weight;  // 赋权值
		hf[con + i].lchild = minsub1;    // 连接两个子节点
		hf[con + i].rchild = minsub2;
		hf[con + i].parent = -1;

		hf[minsub1].parent = con + i;     // 为子节点赋值父节点
		hf[minsub2].parent = con + i;
	}
}


//  获取Huffman编码
void GetHuffmanCode(PHT hf, PPCHAR str, int* weight, int con)
{

	char* tmp = (char*)malloc(con);    // 记录每个叶子结点的编码
	if (tmp == NULL)
		exit(-1);

	tmp[con - 1] = '\0';         //   字符串结束
	// 叶子结点位置就是前con个
	for (int i = 0; i < con; i++)
	{
		int t = con - 2;        // tmp有效下标
		int child = i;              //   从叶子结点向上遍历
		int parent = hf[i].parent;
		weight[i] = hf[i].weight;  // 权值
		int len = 0;     // 记录经过的路径个数
		while (parent != -1)
		{
			len++;
			if (hf[parent].lchild == child)    //   左孩子编码为‘0’,右孩子编码为‘1’
				tmp[t--] = '0';
			else
				tmp[t--] = '1';

			child = parent;        // 孩子和父亲都向上走,更新下标
			parent = hf[parent].parent;
		}
		weight[i] *= len;                     //    该节点的WPL

		str[i] = (char*)malloc(len + 1);  //  编码和 '\0' 总数
		if (str[i] == NULL)
			exit(-1);

		strcpy(str[i], tmp + t + 1);         //  将有效字符拷贝过去
	}

	free(tmp);
}

void HuffmanCodePrint(PHT hf, PPCHAR str, int* weight, int con)
{
	printf("data\tweight\tcode\n");
	int i = 0;
	for (int i = 0; i < con; i++)
	{
		printf("%c\t%d\t%s\n", hf[i].data, weight[i], str[i]);
	}
}

int main()
{
	PHT hf;        // 节点数组
	PPCHAR str;     //  二级指针,字符数组数组
	int* weight;    // 存放每个叶子节点的带权路径长度
	int con = 0; // 叶子节点个数
	printf("请输入待编码结点个数:");
	scanf("%d", &con);
	getchar();   //吃掉换行

	hf = (PHT)malloc(sizeof(HTNode) * (con * 2 - 1));
	if (hf == NULL)
		exit(-1);

	str = (PPCHAR)malloc(sizeof(char*) * con);
	if (str == NULL)
		exit(-1);

	weight = (int*)malloc(sizeof(int) * con);
	if (weight == NULL)
		exit(-1);

	InitHuffmanNode(hf, con);
	CreateHuffman(hf, con);
	GetHuffmanCode(hf, str, weight, con);
	HuffmanCodePrint(hf, str, weight, con);

	free(hf);
	for (int i = 0; i < con; i++)
		free(str[i]);
	free(str);
	free(weight);
	return 0;
}

运行实例:
在这里插入图片描述


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【哈夫曼树】 的相关文章

随机推荐

  • Nacos启动报错

    Nacos启动报错问题的解决方案 nacos官网得知环境要求为jdk1 8 maven3 2 x 为了避免采坑 版本尽量使用官网推荐的 直接上报错 如下 org springframework beans factory Unsatisfi
  • 分配给套接字的IP地址与端口号

    文章目录 1 网络地址 Internet Address 2 网络地址分类与主机地址边界 3 用于区分套接字的端口号 IP 是 Internet Protocol 网络协议 的简写 是为收发网络数据而分配给计算机的值 端口号并非赋予计算机的
  • 五年程序员人生的点点滴滴

    转自 http blog csdn net linux loajie article details 7672455 和大家一样 我也是一名普通的程序员 很快工作五年了 现在依然记得大学时软件工程老师曾说过的一句话 大概是这样的 工作五年之
  • 如何在windows下安装与配置Appium

    如何在windows下安装与配置Appium appium是一款open source 移动自动化测试框架 既支持Android 也支持IOS 工具 原料 JDK adt bundle windows node python appium
  • Grafana(据说全网最详细配置教程)

    见我笔记 https www wolai com fishman tolearn ccb6Z7P4kBr1JQ3m1r2CLs theme light
  • Java中的序列化和反序列化

    java对象序列化是指将java对象转换为字节序列的过程 而反序列化则是将字节序列转换为java对象的过程 我们知道 不同进程 程序间进行远程通信时 可以相互发送各种类型的数据 包括文本 图片 音频 视频等 而这些数据都会以二进制序列的形式
  • Blob,ArrayBuffer,File,FileReader,Buffer,TypeArray 的作用和区别

    Blob Binary Large object 二进制大型对象 是一个相对high level的概念 一个Blob对象可以包含一个或多个连续内存 通常是由一个或多个ArrayBuffer对象组成的数组 ArrayBufer 与 Buffe
  • kubernetes HPA使用及测试

    一 安装metrics server Metrics Server是Kubernetes内置的容器资源指标来源 Metrics Server从node节点上的Kubelet收集资源指标 并通过Metrics API在 Kubernetes
  • TestNG基本注释二:基本注释解释

    在TestNG基本注释一中 我们给出来一个用eclipse IDE生成的TestNG测试类 package test java com testng test import org testng annotations Test impor
  • 机器学习实战5-天气预测系列:利用数据集可视化分析数据,并预测某个城市的天气情况

    大家好 我是微学AI 最近天气真的是多变啊 忽冷忽热 今天再次给大家带来天气的话题 机器学习实战5 天气预测系列 我们将探讨一个城市的气象数据集 并利用机器学习来预测该城市的天气状况 该数据集包含年平均温度和湿度等信息 一 准备工作 首先
  • 【Linux】Ubuntu20.04版本安装谷歌中文输入法【教程】

    Linux Ubuntu20 04版本安装谷歌中文输入法 教程 文章目录 Linux Ubuntu20 04版本安装谷歌中文输入法 教程 一 下载fcitx googlepinyin 二 配置Language Support Referen
  • java随机生成6位验证码的方法对比(这里列出三种)

    第一种方式 不推荐 因为结果可能会出现错误 String code String valueOf new Random nextInt 1000000 这种方式有问题 问题在于 在连续生成多次的情况下 可能会生成小于6位的验证码 测试 fo
  • scrollIntoView 的使用

    描述 将调用此方法的元素滚动到浏览器窗口的可见区域 scrollIntoView 官方文档 用法 element scrollIntoView 用法同 element scrollIntoView true element scrollIn
  • 网络工程师--网络安全与应用案例分析

    案例一 某单位现有网络拓扑结构如下图所示 实现用户上网功能 该网络使用的网络交换机均为三层设备 用户地址分配为手动指定 案例分析一 路由器AR2200的GE0 0 1接口地址为内网地址 为确保内部用户访问Internet 需要在该设备配置
  • 线上验证真的就是点点点吗?

    最近测试了一个项目 与其他4个后台有合作 今天项目上线了 一下配置错了 一下数据错了 真整的有点手忙脚乱 于是 整理了一个checklist 供后续备忘 希望能对大家有所启发 线上验证 有些项目比较单一 或许点点点就足够了 但是遇到与其他项
  • SHELL脚本 遍历文件夹下所有文件以及子文件夹

    SHELL脚本 遍历文件夹下所有文件以及子文件夹 dir 要设置为局部变量 如果设置为全局变量 在func递归时传入的参数 会改变 dir的值 将导致之后的文件目录错误 为更改后的dir值 当前目录情况 执行完shell后 附上代码 bin
  • 从源码到原理剖析activity核心知识点

    如何在onResume方法中获取到View的宽高 有两种方式 post和addOnGlobalLayoutListener override fun onResume super onResume Log e onresume tabBot
  • 新手使用~React+Antd^4.1.3+Hooks自定义筛选框

    官网此版本的筛选组件为 根据可爱的pm的要求 需要添加全选 确认筛选 重置按钮 此情景适用于后端所需要的的参数传递为多选 数组的形式 而非单个字符串 引入需要的组件及包 import Table Button Checkbox Space
  • AAL:ATM 适配层(AAL0、AAL2、AAL3/4、AAL5)--网络大典

    ATM 适配层 AAL 主要负责 ATM 层与高层之间的信元转发过程 从上层收到信息后 AAL 将数据分割成 ATM 信元 从 ATM 层收到信息后 AAL 必须重新组合数据形成一个上层能够辨识的格式 上述操作称之为分段与重组 SAR 它是
  • 【哈夫曼树】

    目录 前言 1 哈夫曼树结构 2 初始化 3 构造哈夫曼树 4 获取Huffman编码 整体代码 前言 哈夫曼树又称最优二叉树 可以对带权节点进行编码并且保证每个数据的编码都不会是其他数据的前缀 保证了编码的唯一性 因此 哈夫曼编码又称为前