赫夫曼树和赫夫曼编码详解
目录
何为赫夫曼树?
赫夫曼树算法
赫夫曼编码
编程实现赫夫曼树
编程实现赫夫曼编码
编程实现WPL
总代码及分析
何为赫夫曼树?
树的路径长度:从树根到每一结点的路径长度之和
结点的带权路径长度:从树根到该结点的路径长度与结点上权的乘积
树的带权路径长度:树中所有叶子结点的带权路径长度之和

假设有n个权值(w1.w2,,,Wn),试构造一棵有n个叶子结点的二叉树,每个叶子结点带权为Wi,则其中WPL最小的二叉称做最优二叉树或赫夫曼树。
赫夫曼树算法
(1)根据给定的n个权值(w1,w2,…,Wn,)构成n棵二叉树的集合F=(T1,T2,…,Tn,其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树均空。
(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。
(3)在F中删除这两棵树,同时将新得到的二叉树加入F中。
(4)重复(2)和(3),直到F只含一棵树为止。这棵树便是赫夫曼树。
如下图:

赫夫曼编码
设一课二叉树为:

其3个叶子结点分别表示a、b、c3个字符, 约定左分支表示字符‘0’,右分支表示字符‘1’。则可以从根结点到叶子结点的路径上分支字符组成的字符串作为该叶子结点字符的编码。如上图可以得到a、b、c的二级制前缀编码分别为:0、10、11。
如何得到使电文总长最短的二级制编码?假设每种字符在电文中出现的次数为i,编码长度为j,而电文只有n种字符,则电文总长为n种i*j的和。对应到二叉树上,若i表示叶子结点的权值,j为从根到叶子的路径长度。则电文总长恰为二叉树带权路径长度。所以,设计电文总长最短的二级制前缀编码即以N种字符出现的频率作为权值,设计一颗赫夫曼树的问题
编程实现赫夫曼树
//哈夫曼树的存储表示
typedef struct
{int weight; //节点的权值int parent, lchild, rchild; //节点的双亲,左孩子和右孩子
} HTNode, * HuffmanTree;typedef char** HuffmanCode;//查权值最小且双亲为0的节点
void Select(HuffmanTree HT, int len, int& s1, int& s2)
{int i, min1 = 0x3f3f3f3f, min2 = 0x3f3f3f3f; //先赋予最大值for (i = 1; i <= len; i++){if (HT[i].weight < min1 && HT[i].parent == 0){min1 = HT[i].weight;s1 = i;}}int temp = HT[s1].weight; //将原值存放起来,然后先赋予最大值,防止s1被重复选择HT[s1].weight = 0x3f3f3f3f;for (i = 1; i <= len; i++){if (HT[i].weight < min2 && HT[i].parent == 0){min2 = HT[i].weight;s2 = i;}}HT[s1].weight = temp; //恢复原来的值
}void CreatHuffmanTree(HuffmanTree& HT, int n,int*w)
{//构造赫夫曼树HTint m, s1, s2, i;if (n <= 1)return;m = 2 * n - 1;HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点for (i = 1; i <= m; ++i) //将1~m号单元中的权重、双亲、左孩子,右孩子的下标都初始化为0{HT[i].parent = 0;HT[i].lchild = 0;HT[i].rchild = 0;HT[i].weight = 0;}for (i = 1; i <= n; ++i) //输入前n个单元中叶子结点的权值{HT[i].weight = *w;w++;}/*――――――――――初始化工作结束,下面开始创建赫夫曼树――――――――――*/for (i = n + 1; i <= m; ++i){ //通过n-1次的选择、删除、合并来创建赫夫曼树Select(HT, i - 1, s1, s2);//在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,// 并返回它们在HT中的序号s1和s2HT[s1].parent = i;HT[s2].parent = i;//这里双亲不为0,相当于把s1和s2删除//得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为iHT[i].lchild = s1;HT[i].rchild = s2; //s1,s2分别作为i的左右孩子HT[i].weight = HT[s1].weight + HT[s2].weight; //i 的权值为左右孩子权值之和}
}
编程实现赫夫曼编码
/*
从叶子节点到根节点逆向求赫夫曼树HT中n个叶子节点的赫夫曼编码,并保存在HC中
*/
void HuffmanCoding(HuffmanTree&HT, HuffmanCode& HC, int n)
{//用来保存指向每个赫夫曼编码串的指针HC = (HuffmanCode)malloc((n+1) * sizeof(char*));if (!HC){exit(-1);}//临时空间,用来保存每次求得的赫夫曼编码串//对于有n个叶子节点的赫夫曼树,各叶子节点的编码长度最长不超过n-1//外加一个'\0'结束符,因此分配的数组长度最长为n即可char* code = (char*)malloc(n * sizeof(char));if (!code){printf("code malloc faild!");exit(-1);}code[n - 1] = '\0'; //编码结束符,亦是字符数组的结束标志//求每个字符的赫夫曼编码int i;for (i = 1; i <= n; i++){int current = i; //定义当前访问的节点int father = HT[i].parent; //当前节点的父节点int start = n - 1; //每次编码的位置,初始为编码结束符的位置//从叶子节点遍历赫夫曼树直到根节点while (father !=0){if (HT[father].lchild == current) //如果是左孩子,则编码为0code[--start] = '0';else //如果是右孩子,则编码为1 code[--start] = '1';current = father;father = HT[father].parent;}//为第i个字符的编码串分配存储空间HC[i] = (char*)malloc((n - start) * sizeof(char));if (!HC[i]){printf("HC[i] malloc faild!");exit(-1);}//将编码串从code复制到HCstrcpy(HC[i], code + start);}for (int i = 1; i <=n; ++i) {printf("%s\n", HC[i]);}free(code); //释放保存编码串的临时空间
}
编程实现WPL
从叶子结点开始遍历二叉树直到根结点,根结点为HT[2n-1],且HT[2n-1].parent=0;
各叶子结点为HT[1]、HT[2]...HT[n]。
关键步骤是求出各个叶子结点的路径长度,用此路径长度*此结点的权值就是
此结点带权路径长度,最后将各个叶子结点的带权路径长度加起来即可。
int countWPL1(HuffmanTree HT, int n)
{int i, countRoads, WPL = 0;/*由creat_huffmanTree()函数可知,HT[1]、HT[2]...HT[n]存放的就是各个叶子结点,所以挨个求叶子结点的带权路径长度即可*/for (i = 1; i <=n; i++){int father = HT[i].parent; //当前节点的父节点countRoads = 0;//置当前路径长度为0//从叶子节点遍历赫夫曼树直到根节点while (father != 0){countRoads++;father = HT[father].parent;}WPL += countRoads * HT[i].weight;}return WPL;
}
总代码及分析
#include <stdio.h>
#include<stdlib.h>
#include<string>
//哈夫曼树的存储表示
typedef struct
{int weight; //节点的权值int parent, lchild, rchild; //节点的双亲,左孩子和右孩子
} HTNode, * HuffmanTree;
typedef char** HuffmanCode;
//查权值最小且双亲为0的节点
void Select(HuffmanTree HT, int len, int& s1, int& s2)
{int i, min1 = 0x3f3f3f3f, min2 = 0x3f3f3f3f; //先赋予最大值for (i = 1; i <= len; i++){if (HT[i].weight < min1 && HT[i].parent == 0){min1 = HT[i].weight;s1 = i;}}int temp = HT[s1].weight; //将原值存放起来,然后先赋予最大值,防止s1被重复选择HT[s1].weight = 0x3f3f3f3f;for (i = 1; i <= len; i++){if (HT[i].weight < min2 && HT[i].parent == 0){min2 = HT[i].weight;s2 = i;}}HT[s1].weight = temp; //恢复原来的值
}void CreatHuffmanTree(HuffmanTree& HT, int n,int*w)
{//构造赫夫曼树HTint m, s1, s2, i;if (n <= 1)return;m = 2 * n - 1;HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点for (i = 1; i <= m; ++i) //将1~m号单元中的权重、双亲、左孩子,右孩子的下标都初始化为0{HT[i].parent = 0;HT[i].lchild = 0;HT[i].rchild = 0;HT[i].weight = 0;}for (i = 1; i <= n; ++i) //输入前n个单元中叶子结点的权值{HT[i].weight = *w;w++;}/*――――――――――初始化工作结束,下面开始创建赫夫曼树――――――――――*/for (i = n + 1; i <= m; ++i){ //通过n-1次的选择、删除、合并来创建赫夫曼树Select(HT, i - 1, s1, s2);//在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,// 并返回它们在HT中的序号s1和s2HT[s1].parent = i;HT[s2].parent = i;//这里双亲不为0,相当于把s1和s2删除//得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为iHT[i].lchild = s1;HT[i].rchild = s2; //s1,s2分别作为i的左右孩子HT[i].weight = HT[s1].weight + HT[s2].weight; //i 的权值为左右孩子权值之和}
} // CreatHuffmanTree
/*
从叶子节点到根节点逆向求赫夫曼树HT中n个叶子节点的赫夫曼编码,并保存在HC中
*/
void HuffmanCoding(HuffmanTree&HT, HuffmanCode& HC, int n)
{//用来保存指向每个赫夫曼编码串的指针HC = (HuffmanCode)malloc((n+1) * sizeof(char*));if (!HC){exit(-1);}//临时空间,用来保存每次求得的赫夫曼编码串//对于有n个叶子节点的赫夫曼树,各叶子节点的编码长度最长不超过n-1//外加一个'\0'结束符,因此分配的数组长度最长为n即可char* code = (char*)malloc(n * sizeof(char));if (!code){printf("code malloc faild!");exit(-1);}code[n - 1] = '\0'; //编码结束符,亦是字符数组的结束标志//求每个字符的赫夫曼编码int i;for (i = 1; i <= n; i++){int current = i; //定义当前访问的节点int father = HT[i].parent; //当前节点的父节点int start = n - 1; //每次编码的位置,初始为编码结束符的位置//从叶子节点遍历赫夫曼树直到根节点while (father !=0){if (HT[father].lchild == current) //如果是左孩子,则编码为0code[--start] = '0';else //如果是右孩子,则编码为1 code[--start] = '1';current = father;father = HT[father].parent;}//为第i个字符的编码串分配存储空间HC[i] = (char*)malloc((n - start) * sizeof(char));if (!HC[i]){printf("HC[i] malloc faild!");exit(-1);}//将编码串从code复制到HCstrcpy(HC[i], code + start);}for (int i = 1; i <=n; ++i) {printf("%s\n", HC[i]);}free(code); //释放保存编码串的临时空间
}
/*
从叶子结点开始遍历二叉树直到根结点,根结点为HT[2n-1],且HT[2n-1].parent=0;
各叶子结点为HT[1]、HT[2]...HT[n]。
关键步骤是求出各个叶子结点的路径长度,用此路径长度*此结点的权值就是
此结点带权路径长度,最后将各个叶子结点的带权路径长度加起来即可。
*/
int countWPL1(HuffmanTree HT, int n)
{int i, countRoads, WPL = 0;/*由creat_huffmanTree()函数可知,HT[1]、HT[2]...HT[n]存放的就是各个叶子结点,所以挨个求叶子结点的带权路径长度即可*/for (i = 1; i <=n; i++){int father = HT[i].parent; //当前节点的父节点countRoads = 0;//置当前路径长度为0//从叶子节点遍历赫夫曼树直到根节点while (father != 0){countRoads++;father = HT[father].parent;}WPL += countRoads * HT[i].weight;}return WPL;
}int main()
{HuffmanTree HT;HuffmanCode HC;int n;printf("请输入叶子结点的个数:\n");scanf("%d", &n);int w[8] = { 2, 15, 30, 8, 10, 5, 12, 18 };CreatHuffmanTree(HT, n,w);printf("哈夫曼树建立完毕!\n");printf("各字符赫夫曼编码为:\n");HuffmanCoding(HT, HC, n);printf("带权路径最小为:\n");printf("%d", countWPL1(HT, n));}
测试:

测试中的数据结构:

HT:
初始化后
创建后:
HC
相关文章:
赫夫曼树和赫夫曼编码详解
目录 何为赫夫曼树? 赫夫曼树算法 赫夫曼编码 编程实现赫夫曼树 编程实现赫夫曼编码 编程实现WPL 总代码及分析 何为赫夫曼树? 树的路径长度:从树根到每一结点的路径长度之和 结点的带权路径长度:从树根到该结点的路径长度…...
unity UGUI系统梳理 -交互组件
概述 unity 中的交互组件可用于处理交互,例如鼠标或触摸事件以及使用键盘或控制器进行的交互 1、按钮 (Button) Button详解 2、开关 (Toggle) Background:背景图片,控制toggle组件的背景颜色改变,从而展示此物体是否被选中的…...
HTTP第15讲——HTTP的连接管理
短连接 HTTP 协议最初(0.9/1.0)是个非常简单的协议,通信过程也采用了简单的“请求 - 应答”方式。 它底层的数据传输基于 TCP/IP,每次发送请求前需要先与服务器建立连接,收到响应报文后会立即关闭连接。 因为客户端与…...
深度剖析Mybatis-plus Injector SQL注入器
背景 在项目中需要同时操作Sql Server 以及 MySQL 数据库,可能平时直接使用 BaseMapper中提供的方法习惯 了,不用的话总感觉影响开发效率,但是两个数据库的SQL语法稍微有点差别,有些暴露的方法并不能直接使用,所以便想…...
【Mysql实战】使用存储过程和计算同比环比
背景 同环比,是基本的数据分析方法。在各类调研表中屡见不鲜,如果人工向前追溯统计数据,可想而知工作量是非常大的。 标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数【Mysql实战】使用存储过程和计算同比环比…...
ChatGPT的前世今生,到如今AI领域的竞争格局,本文带你一路回看!
73年前,“机器思维”的概念第一次被计算机科学之父艾伦图灵(Alan Turing)提出,从此,通过图灵测试成为了人类在AI领域为之奋斗的里程碑目标。 73年后的今天,在AI历经了数十年的不断进化、迭代后,…...
如何在JavaScript中获取当前时间yyyymmddhhmmss? (六种实现方式)
## 介绍 在编写JavaScript代码时,我们经常需要获取当前日期和时间。在本文中,我们将介绍几种获取当前时间并将其格式化为 yyyymmddhhmmss 的字符串的方法。 方法一:使用Date对象 在JavaScript中,我们可以使用 Date 对象来获取当…...
一、走进easyUI的世界
1.什么是easyUI? jQuery EasyUI是一组基于jQuery的UI插件集合体,而jQuery EasyUI的目标就是帮助web开发者更轻松的打造出功能丰富并且美观的UI界面。开发者不需要编写复杂的javascript,也不需要对css样式有深入的了解,开发者需要…...
2023 上半年软件设计师知识点复习总纲
前言:全国计算机技术与软件专业技术资格(水平)考试(以下简称IT职业资格考试)是由中华人民共和国人事部主管,国家计算机网络与信息安全管理中心主办的一项国家级、权威性的计算机职业技能水平认证考试。主要…...
深入理解Java虚拟机:JVM高级特性与最佳实践-总结-3
深入理解Java虚拟机:JVM高级特性与最佳实践-总结-3 垃圾收集器与内存分配策略垃圾收集算法标记-清除算法标记-复制算法标记-整理算法 垃圾收集器与内存分配策略 垃圾收集算法 标记-清除算法 最基础的垃圾收集算法是“标记-清除”(Mark-Sweepÿ…...
vue3 cesium datav 可视化大屏
目录 0. 预览效果 1. 代码库包 2. 技术点 3. 一些注意事项(配置参数) 4. 相关代码详情 0. 预览效果 包含的功能: ① 地球按照一定速度自转 ② 修改加载的geojson面样式 ③ 添加 文字 标注! 1. 代码库包 直接采用vue-cli5 创建…...
python内置函数,推导式
abs:取绝对值 data abs(-10) pow:次方 data pow(2,5) sum:求和 num_list p[1,2,10,20] res sum(num_list) divmod取商和余数: v1,v2 divmod&…...
【Flink】DataStream API使用之Flink支持的数据类型
Flink的使用过程中,我们的数据都是定义好的 UserBehavior 类型,那还有没有其他更灵活的类型可以用呢?Flink 支持的数据类型到底有哪些? 1. Flink 的类型系统 Flink 作为一个分布式处理框架,处理的是以数据对象作为元…...
QT实现固高运动控制卡示波器
目录 一、固高示波器 二、基于QCustomPlot实现示波器 三、完整源码 一、固高示波器 固高运动控制卡自带的软件有一个示波器功能,可以实时显示速度的波形,可辅助分析电机的运行状态。但是我们基于sdk开发了自己的软件,无法再使用该功能&…...
洛谷P1157详解(两种解法,一看就会)
一、问题引出 组合的输出 题目描述 排列与组合是常用的数学方法,其中组合就是从 n n n 个元素中抽出 r r r 个元素(不分顺序且 r ≤ n r \le n r≤n),我们可以简单地将 n n n 个元素理解为自然数 1 , 2 , … , n 1,2,\dot…...
JavaScript异步编程和回调
目录 1、编程语言中的异步 2、JavaScript 3、回调 3.1在回调中处理错误 3.2回调的问题 3.2回调的替代方案 1、编程语言中的异步 默认情况下,JavaScript是同步的,并且是单线程…...
Qt开发笔记(Qt5.9.9下载安装环境搭建win10)
#1 Qt下载网站(国内、国外镜像) #2 Qt5.9.9安装选项 #3 配置系统环境变量 #4 创建测试项目 #1 Qt下载网站(国内、国外镜像) 官方下载地址(慢):http://download.qt.io/ 国内镜像网站 这里给大家…...
使用Plist编辑器——简单入门指南
本指南将介绍如何使用Plist编辑器。您将学习如何打开、编辑和保存plist文件,并了解plist文件的基本结构和用途。跟随这个简单的入门指南,您将掌握如何使用Plist编辑器轻松管理您的plist文件。 plist文件是一种常见的配置文件格式,用于存储应…...
Python常用的开发工具合集
Python是一种功能强大且易于学习的编程语言,被广泛应用于数据科学、机器学习、Web开发等领域。随着Python在各个领域的应用越来越广泛,越来越多的Python开发工具也涌现出来。但是,对于新手来说,选择一款合适的Python开发工具可…...
机器学习之线性回归
往期目录 python在线性规划中的应用 文章目录 一、线性回归算法概述1.1 什么是线性回归?1.2 线性回归算法原理1.3 线性回归的应用场景 二、线性回归算法Python实现2.1 导入必要的库2.2 随机生成数据集2.3 拟合模型2.4 预测结果2.5 结果可视化 三、完整代码 线性回归…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
针对药品仓库的效期管理问题,如何利用WMS系统“破局”
案例: 某医药分销企业,主要经营各类药品的批发与零售。由于药品的特殊性,效期管理至关重要,但该企业一直面临效期问题的困扰。在未使用WMS系统之前,其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...
CTF show 数学不及格
拿到题目先查一下壳,看一下信息 发现是一个ELF文件,64位的 用IDA Pro 64 打开这个文件 然后点击F5进行伪代码转换 可以看到有五个if判断,第一个argc ! 5这个判断并没有起太大作用,主要是下面四个if判断 根据题目…...
CppCon 2015 学习:Reactive Stream Processing in Industrial IoT using DDS and Rx
“Reactive Stream Processing in Industrial IoT using DDS and Rx” 是指在工业物联网(IIoT)场景中,结合 DDS(Data Distribution Service) 和 Rx(Reactive Extensions) 技术,实现 …...
