【数据结构】- 详解哈夫曼树(用 C 语言实现哈夫曼树的构造和哈夫曼编码)

目录

一、哈夫曼树的基本概念

二、哈夫曼树的构造算法

2.1 - 哈夫曼树的构造过程

2.2 - 哈夫曼树的存储表示

2.3 - 算法实现

三、哈夫曼编码

3.1 - 哈夫曼编码的主要思想

3.2 - 哈夫曼编码的性质

3.3 - 算法实现


 


一、哈夫曼树的基本概念

哈夫曼树的定义,涉及路径、路径长度、权等概念,下面先给出这些概念的定义,然后再介绍哈夫曼树。

  1. 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。

  2. 路径长度:路径上的分支数目称作路径长度。

  3. 树的路径长度:从树根到每一结点的路径长度之和。

  4. :赋予某个实体的一个量,对实体的某个或某些属性的数值化描述。在数据结构中,实体有结点(元素)和边(关系)两大类,所以对应有结点权边权。结点权和边权具体代表什么意义,由具体情况决定。如果在一棵树中的结点上带有权值,则对应就有带权树等概念

  5. 结点的带权路径长度:从树根到该结点的路径长度与该结点上权值的乘积。

  6. 树的带权路径长度:树中所有叶子结点的带权路径长度之和,通常记作 。

在含有 n 个叶子结点的二叉树中,每个叶子结点的权值为 ,则其中 WPL 最小的二叉树称作最优二叉树或哈夫曼(Huffman)树

例如,下图所示的 3 棵二叉树中,都含有 4 个叶子结点 a、b、c、d,分别带权 7、5、2、4。

其中 (c) 树的带权路径长度最小,可以验证,它恰为哈夫曼树。

哈夫曼树中具有不同权值的叶子结点的分布有什么特点呢?从上面的例子中,可以直观地发现,在哈夫曼树中,权值越大的结点离根结点越近。根据这个特点,哈夫曼最早给出了一个构造哈夫曼树的方法,称哈夫曼算法。


二、哈夫曼树的构造算法

2.1 - 哈夫曼树的构造过程

  1. 根据给定的 n 个权值 ,构造 n 棵只有根结点的二叉树,这 n 棵二叉树构成一个森林 F(Forest)。

  2. 在森林 F 中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。

  3. 在森林 F 中删除这两棵树,同时将新得到的二叉树加入 F 中。

  4. 重复 2 和 3,直到 F 中只含一棵树为止,这棵树便是哈夫曼树。

在构造哈夫曼树时,首先选择权值小的结点,这样保证权值大的结点离根结点较近,这样一来,在计算树的带权路径长度时,自然会得到最小带权路径长度,这种生成算法是一种典型的贪心法

例如,下图所示为上图 (c) 所示的哈夫曼树的构造过程。其中,根结点上标注的数字是所赋的权。

注意:哈夫曼树并不唯一,但 WPL 必然相同且最优

2.2 - 哈夫曼树的存储表示

typedef struct HTNode
{int weight;  // 结点的权值int parent;  // 结点的双亲的下标int left;  // 结点的左孩子的下标int right;  // 结点的右孩子的下标
}HTNode;
​
typedef struct HTree
{HTNode* data;int size;
}HTree;

哈夫曼树中的各结点存储在 data 指向的一个大小为 2n - 1 的动态分配的数组中

解释 1:n 个叶子结点进行 n - 1 次合并,生成 n - 1 个度为 2 的新结点,所以总结点数 N 为 2n - 1

解释 2:因为哈夫曼树中没有度为 1 的结点,所以一棵有 n 个叶子结点的哈夫曼树总共有 2n - 1 结点,即 N = N0 + N1 + N2 = 2N0 - 1 = 2n - 1

将叶子结点集中存储在 data[0] ~ data[n - 1] 中,将非叶子结点存储在 data[n] ~ data[2n - 2] 中

2.3 - 算法实现

构造哈夫曼树算法的实现可以分为两大部分:

  1. 初始化:首先动态申请 2n - 1 个单元,然后循环 2n - 1 次,将所有单元中结点的双亲、左孩子、右孩子的下标都初始化为 -1,如果是前 n 个单元,还要初始化这 n 个单元中叶子结点的权值。

  2. 创建树:循环 n - 1 次,通过 n - 1 次的选择、删除与合并来创建哈夫曼树。

HuffmanTree.c

#include "HuffmanTree.h"
#include <stdlib.h>
​
// 在 HT->data[0] ~ HT->data[end] 中选择两个双亲的下标为 -1 且权值最小的结点,
// 并通过输出型参数 pMinIndex1 和 pMinIndex2 返回它们在 HT->data 中的下标
void Select(HTree* HT, int end, int* pMinIndex1, int* pMinIndex2)
{int min1 = INT_MAX;for (int i = 0; i <= end; ++i){if (HT->data[i].parent == -1 && HT->data[i].weight < min1){min1 = HT->data[i].weight;*pMinIndex1 = i;}}int min2 = INT_MAX;for (int i = 0; i <= end; ++i){if (i != *pMinIndex1 && HT->data[i].parent == -1 && HT->data[i].weight < min2){min2 = HT->data[i].weight;*pMinIndex2 = i;}}
}
​
HTree* CreateHuffmanTree(int* weight, int n)
{// 初始化HTree* HT = (HTree*)malloc(sizeof(HTree));HT->data = (HTNode*)malloc(sizeof(HTNode) * (2 * n - 1));HT->size = 2 * n - 1;for (int i = 0; i < 2 * n - 1; ++i){if (i < n)HT->data[i].weight = weight[i];
​HT->data[i].parent = -1;HT->data[i].left = -1;HT->data[i].right = -1;}// 创建树for (int i = n; i < 2 * n - 1; ++i){int minIndex1, minIndex2;// 选择Select(HT, i - 1, &minIndex1, &minIndex2); // 删除HT->data[minIndex1].parent = i;HT->data[minIndex2].parent = i;// 合并HT->data[i].left = minIndex1;HT->data[i].right = minIndex2;HT->data[i].weight = HT->data[minIndex1].weight + HT->data[minIndex2].weight;}return HT;
}

Test.c

#include "HuffmanTree.h"  // 包含了哈夫曼树的存储表示以及函数声明
#include <stdio.h>
#include <stdlib.h>
​
void PreOrder(HTree* HT, int rootIndex)
{if (rootIndex == -1)return;
​printf("%d ", HT->data[rootIndex].weight);PreOrder(HT, HT->data[rootIndex].left);PreOrder(HT, HT->data[rootIndex].right);
}
​
void DestroyHuffmanTree(HTree* HT)
{free(HT->data);HT->data = NULL;HT->size = 0;
}
​
int main()
{int weight[8] = { 5, 29, 7, 8, 14, 23, 3, 11 };HTree* HT = CreateHuffmanTree(weight, 8);PreOrder(HT, HT->size - 1);// 100 42 19 8 3 5 11 23 58 29 29 14 15 7 8printf("\n");DestroyHuffmanTree(HT);return 0;
}

 


三、哈夫曼编码

3.1 - 哈夫曼编码的主要思想

在数据通信、数据压缩问题中,需要将数据文件转换成二进制字符 0、1 组成的二进制串,称之为编码

假设待压缩的数据为 "abcdabcdaaaaabbbdd",数据包含 18 个字符,其中只有 a(7 个)、b(5 个)、c(2 个)、d(4 个) 四种字符,如果采用等长编码,每个字符编码取两位即可,编码总长度为 36 位。下表所示为一种等长编码方案。

字符编码
a00
b01
c10
d11

但这并非最优的编码方案,因为每个字符出现的频率不同,如果在编码时考虑字符出现的频率,使频率高的字符采用尽可能短的编码,频率低的字符采用稍长的编码,来构造一种不等长编码,则会获得更好的空间效率,这也是文件压缩技术的核心思想。下表所示为一种不等长编码方案,采用这种编码方案,编码总长度为 35 位。

字符编码
a0
b10
c110
d111

但是对于不等长编码,如果设计得不合理,便会给解码带来困难。例如,对于下表所示的另一种不等长编码方案。

字符编码
a0
b01
c010
d111

采用该编码方案后,上述数据编码后为 "00101111100101111100000010101111111"。但是这样的编码数据无法翻译,例如,传过去的字符串中前 4 个字符的子串 "0010" 就可有不同的译法,或是 "aba",或是 "ac"。因此,若要设计长度不等的编码,必须满足一个条件:任何一个字符的编码都不是另一个字符的编码的前缀(最左子串)

那么如何设计有效的用于数据压缩的二进制编码呢?我们可以利用哈夫曼树来设计。第二个表格所示的编码是以字符 a、b、c、d 在数据串 "abcdabcdaaaaabbbdd" 中出现的次数 7、5、2、4 为权值,构造下图所示的哈夫曼树,约定左分支标记为 0,右分支标记为 1,则根结点到每个叶子结点路径上的 0、1 序列即为相应字符的编码

a、b、c、d 的哈夫曼编码分别为 0、10、110 和 111。

3.2 - 哈夫曼编码的性质

  1. 性质一:哈夫曼编码是前缀编码

    前缀编码:如果在一个编码方案中,任一个编码都不是其他任何编码的前缀(最左子串),则称编码是前缀编码。

    证明:哈夫曼编码是从根结点到叶子结点路径上的编码序列,由树的特点可知,若路径 A 是另一条路径 B 的左部分,则 B 经过了 A,则 A 的终点一定不是叶子结点。而哈夫曼编码对应路径的终点一定为叶子结点,因此,任一哈夫曼编码都不会与任意其他哈夫曼编码的前缀部分完全重叠,因此哈夫曼编码是前缀编码。

  2. 性质二:哈夫曼编码是最优前缀编码

    对于包含 n 个字符的数据文件,分别以它们出现次数为权值构造哈夫曼树,则利用该树对应的哈夫曼编码对文件进行编码,能使文件压缩后对应的二进制文件的长度最短。、

    证明:由哈夫曼树的构造算法可知,出现次数较多的字符对应的编码较短,这便直观地说明了该定理是成立的。

3.3 - 算法实现

在构造哈夫曼树之后,求哈夫曼编码的主要思想是:依次以叶子结点出发,向上回溯至根结点位置。回溯时走左分支则生成代码 0,走右分支则生成代码 1

由于每个哈夫曼编码是变长编码,因此使用一个字符指针数组来存放每个字符编码串的首地址。

#include "HuffmanTree.h"
#include <stdlib.h>
#include <string.h>char** CreateHuffmanCode(HTree* HT)
{int n = (HT->size + 1) / 2;char** HC = (char**)malloc(sizeof(char*) * n);char* tmp = (char*)malloc(sizeof(char) * n);  // 字符编码的长度一定小于 ntmp[n - 1] = '\0';// 逐个求 n 个字符的哈夫曼编码for (int i = 0; i < n; ++i){int pos = n - 2;int cur = i;int parent = HT->data[i].parent;while (parent != -1){if (HT->data[parent].left == cur)tmp[pos--] = '0';elsetmp[pos--] = '1';// 向上回溯cur = parent;parent = HT->data[parent].parent;}HC[i] = (char*)malloc(sizeof(char) * (n - 1 - pos));strcpy(HC[i], &tmp[pos + 1]);}free(tmp);tmp = NULL;return HC;
}

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

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

相关文章

Hazelcast分布式内存网格(IMDG)基本使用,使用Hazelcast做分布式内存缓存

文章目录 一、Hazelcast简介1、Hazelcast概述2、Hazelcast之IMDG3、数据分区 二、Hazelcast配置1、maven坐标2、集群搭建&#xff08;1&#xff09;组播自动搭建 3、客户端4、集群分组5、其他配置 三、Hazelcast分布式数据结构1、IMap2、IQueue&#xff1a;队列3、MultiMap4、I…

网络安全(四)--Linux 主机防火墙

7.1. 介绍 防火墙&#xff08;Firewall&#xff09;&#xff0c;也称防护墙&#xff0c;是由Check Point创立者Gil Shwed于1993年发明并引入国际互联网&#xff08;US5606668&#xff08;A&#xff09;1993-12-15&#xff09;。 它是一种位于内部网络与外部网络之间的网络安全…

人工智能-编译器和解释器

编译器和解释器 命令式编程使用诸如print、“”和if之类的语句来更改程序的状态。 考虑下面这段简单的命令式程序&#xff1a; def add(a, b):return a bdef fancy_func(a, b, c, d):e add(a, b)f add(c, d)g add(e, f)return gprint(fancy_func(1, 2, 3, 4)) 10 Python…

课题学习(十五)----阅读《测斜仪旋转姿态测量信号处理方法》论文

一、 论文内容 1.1 摘要 为准确测量旋转钻井时的钻具姿态&#xff0c;提出了一种新的信号处理方法。测斜仪旋转时&#xff0c;垂直于其旋转轴方向加速度计的输出信号中重力加速度信号分量具有周期性特征&#xff0c;以及非周期性离心加速度分量频率低于重力加速度信号分量频率…

三、jvm中的对象及引用

一、对象在jvm的创建过程 检查加载-->分配内存-->内存空间初始化-->设置-->对象初始化 1) 检查加载 首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查类是否已经被加载、解析和初始化过。 虚拟机遇到一条 new 指令时&#xf…

mybatis的分页插件

在mybatis核心配置文件中&#xff1a; 这时已经用了SSM整合&#xff0c;好多像是mapper或者数据源等都移出去了 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""…

Zabbix自定义飞书webhook告警媒介2

说明:适用于7.0及以上版本,低版本可能会有问题。 参数如下: 名称 值EVENT.DURATION{EVENT.DURATION}EVENTDATE

当使用RSA加密,从手机前端到服务器后端的请求数据存在+

将转成了空格&#xff0c;导致解密出错 将空格转成了

Unity中Batching优化的GPU实例化(1)

文章目录 前言一、GPU实例化的规则1、必须满足 Mesh 网格一样2、只有OpenGL es 3.0及以上才支持&#xff08;3.0及以上有部分硬件可能也不支持&#xff09; 二、GPU实例化的应用场景1、公开几个成员属性&#xff0c;用于存放可以调整的数据2、用Random.insideUnitCircle随机生成…

AWS re:Invent 2023-亚马逊云科技全球年度技术盛会

一:会议地址 2023 re:Invent 全球大会主题演讲 - 亚马逊云科技从基础设施和人工智能/机器学习创新,到云计算领域的最新趋势与突破,倾听亚马逊云科技领导者谈论他们最关心的方面。https://webinar.amazoncloud.cn/reInvent2023/keynotes.html北京时间2023年12月1日00:30-02:…

VSCode之C++ CUDA入门:reduce的N+1重境界

背景 Reduce是几乎所有多线程技术的基础和关键&#xff0c;同样也是诸如深度学习等领域的核心&#xff0c;简单如卷积运算&#xff0c;复杂如梯度聚合、分布式训练等&#xff0c;了解CUDA实现reduce&#xff0c;以及优化reduce是理解CUDA软硬件连接点的很好切入点。 硬件环境&…

HarmonyOS4.0从零开始的开发教程03初识ArkTS开发语言(中)

HarmonyOS&#xff08;二&#xff09;初识ArkTS开发语言&#xff08;中&#xff09;之TypeScript入门 浅析ArkTS的起源和演进 1 引言 Mozilla创造了JS&#xff0c;Microsoft创建了TS&#xff0c;Huawei进一步推出了ArkTS。 从最初的基础的逻辑交互能力&#xff0c;到具备类…

http和https的区别有哪些

目录 HTTP&#xff08;HyperText Transfer Protocol&#xff09; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09; 区别与优势 应用场景 未来趋势 当我们浏览互联网时&#xff0c;我们经常听到两个常用的协议&#xff1a;HTTP&#xff08;HyperText Tra…

Excel 动态拼接表头实现导出

public class Column {//单元格内容private String content;//字段名称&#xff0c;用户导出表格时反射调用private String fieldName;//这个单元格的集合private List<Column> listTpamscolumn new ArrayList<Column>();int totalRow;int totalCol;int row;//exc…

易宝OA 两处任意文件上传漏洞复现

0x01 产品简介 易宝OA系统是一种专门为企业和机构的日常办公工作提供服务的综合性软件平台,具有信息管理、 流程管理 、知识管理(档案和业务管理)、协同办公等多种功能。 0x02 漏洞概述 易宝OA系统UploadFile、BasicService.asmx等接口处存在文件上传漏洞,未授权的攻击者可…

记录 | vscode pyhton c++调试launch.json配置

下面提供 vscode 中 python 和 c 调试配置的 launch.json (好用&#xff0c;已用好几年&#xff0c;建议收藏) {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387&qu…

【Spring】依赖注入之属性注入详解

前言&#xff1a; 我们在进行web开发时&#xff0c;基本上一个接口对应一个实现类&#xff0c;比如IOrderService接口对应一个OrderServiceImpl实现类&#xff0c;给OrderServiceImpl标注Service注解后&#xff0c;Spring在启动时就会将其注册成bean进行统一管理。在Co…

WireShark监控浏览器登录过程网络请求

软件开发中经常前后端扯皮。一种是用Chrome浏览器的开发者工具 来看网络交互&#xff0c;但是前提是 网络端口的确是通的。 WireShark工作在更低层。 这个工具最大的好处&#xff0c;大家别扯皮&#xff0c;看网络底层的log&#xff0c;到底 你的端口开没开&#xff0c; 数据…

计算机图形图像技术(OpenCV核心功能、图像变换与图像平滑处理)

一、实验原理&#xff1a; 1、显示图像 void imshow(const string &name, InputArray image); ①功能&#xff1a;在指定窗口中显示图像。 ②参数&#xff1a;name为窗口的名字&#xff1b;image为待显示的图像。 ③说明&#xff1a;可显示彩色或灰度的字节图像和浮点数图…

Threejs项目实战之一:汽车外观换肤效果三维展示

目录 最终效果1 创建项目2 安装插件3 编写代码3.1 准备工作3.2 代码编写3.2.1 在template标签中构建html页面3.2.2 在style标签中构建页面样式文件3.2.3 在script标签中编写js代码 最终效果 先看下最终实现的效果 接下来&#xff0c;我们就从创建项目开始&#xff0c;一步一步…