数据结构05:树与二叉树[C++][哈夫曼树HuffmanTree]

图源:文心一言

小白友好、代码可跑,但是不一定适合考研~~🥝🥝

第1版:查资料、画导图、画配图~🧩🧩

参考用书:王道考研《2024年 数据结构考研复习指导》

参考用书配套视频:5.5_1_哈夫曼树_哔哩哔哩_bilibili

特别感谢: Chat GPT老师、文心一言老师~


📇目录

📇目录

🦮思维导图 

🧵基本概念

⏲️哈夫曼树简介

🌰构造举栗

⌨️代码实现

🧵分段代码

 🔯P0:调用库文件

 🔯P1:定义结点与指针

 🔯P2:用于优先队列中的比较函数

 🔯P3:构造哈夫曼树

 🔯P4:打印哈夫曼树编码

 🔯P5:计算哈夫曼树的权值路径长度(WPL)

 🔯P6:main函数

🧵完整代码

 🔯P0:完整代码

 🔯P1:执行结果

🔚结语


🦮思维导图 

备注:

  • 思维导图为整理王道教材第5章 查找的所有内容;
  • 本篇仅涉及到哈夫曼树HuffmanTree的代码;
  • 本章节往期博文,涉及到树与二叉树的内容如下~
    • 🌸[树:双亲、孩子、兄弟表示法][二叉树:先序、中序、后序遍历]
    • 🌸数据结构05:树与二叉树[C++][线索二叉树:先序、中序、后序]
    • 🌸数据结构05:树与二叉树[C++][并查集]

🧵基本概念

⏲️哈夫曼树简介

哈夫曼树的起源:

哈夫曼树是由一位美国数学家David A. Huffman在1952年发明的,它的设计灵感来源于信息的编码与传输。哈夫曼树就是一种通过将出现频率高的字符赋予较短编码,从而实现高效编码的数据结构。

哈夫曼树的用途:

首先,哈夫曼树在数据压缩领域中被广泛应用。在我们的日常生活中,常常会遇到需要传输或存储大量数据的情况,比如发送电子邮件、观看在线视频等。而传输或存储数据需要消耗带宽或存储空间,因此我们希望尽可能减少数据的体积。

哈夫曼树通过根据字符的出现频率构建一种最优的编码方式,使得频率高的字符使用较短的二进制编码,而频率低的字符使用较长的二进制编码。这样一来,我们可以在不损失数据的情况下,显著减小数据的体积,从而实现高效的数据压缩,我们可以在网络传输中更高效地传输数据,提升通信的质量和速度。

哈夫曼树的定义:

哈夫曼树是带权路径最小路径长度的二叉树。

带权路径的公式为 = 求和(结点的权值 x 路径长度)

这里的权值实际上就是我们所说的,数字出现的频度~数字在一份文件中出现的频度越高,其权值也就越高~

🌰构造举栗

例如以下三棵树,结点均为“数据a(权值7)、数据b(权值5)、数据c(权值2)、数据d(权值4)”构成:

其构造方式不同,树的权值计算也有差异~ 

  • 树a的WPL:(7+5+2+4)x2=36
  • 树b的WPL:2x1+4x2+(7+5)x3=46
  • 树c的WPL:7x1+5x2+(2+4)x3=35

这要怎么理解这棵树的含义呢?

例如在《天才枪手》中,我们需要利用时差向队友传递一串选择题答案,这个答案包含“7个a、5个b、2个c、4个d”;在通信方面,“a、b、c、d”这4个选择由“0”和“1”这两个数字编码加密组成。

对应上图的哈夫曼树,结点在左子树时编码+0,结点在左子树时编码+1,那么:

  • 树a的编码:a(00)、b(01)、c(10)、d(11),根据权值公式计算的结果,传递选择题答案需要编码36个数字{7个a,也就是7个00,传送所有的a加起来是14个数字,同理所有的b加起来是10个数字、所有的c加起来是4个数字、所有的d加起来是8个数字,传送整个答案就是14+10+4+8=36个数字};
  • 树b的编码:a(010)、b(011)、c(1)、d(00),根据权值公式计算的结果,传递选择题答案需要编码46个数字;
  • 树c的编码:a(0)、b(10)、c(110)、d(111),根据权值公式计算的结果,传递选择题答案需要编码35个数字;

树c的编码相比树b的编码,大概节省了24%的传输量,这就体现出选择树c编码的好处了~

如果不幸选择树b编码的话,会把答案出现频率最高a、b的答案排最长的3字编码(010、011),这在信息传输中显然是非常不合理的~

那如何构成树c呢?

  • 统计字符的出现频率{即各个字符的权值}。实际操作中,这可以通过扫描待编码的数据来实现,统计每个字符出现的次数{以下案例我们还是以传送选择题答案为例,选项a的频率为7,选项b的频率为5、选项c的频率为2、选项d的频率为4考虑}~
  • 将统计得到的每个字符及其对应的频率作为叶节点,构建一个优先队列~
  • 从优先队列中选取频率最小的两个节点,创建一个新的节点作为它们的父节点,并将父节点插入到优先队列中。重复上述步骤,直到优先队列中只剩下一个节点,这个节点就是哈夫曼树的根节点~
  • 通过遍历哈夫曼树,为每个字符生成对应的编码。从根节点开始,左子树路径表示编码位"0",右子树路径表示编码位"1",直到达到叶节点。通过遍历路径,我们可以为每个字符生成唯一的哈夫曼编码

 根据下面的代码,树的具体创建过程如下图:

以图中的小树为例,列出哈夫曼树的构造代码~


图源:文心一言

⌨️代码实现

🧵分段代码

 🔯P0:调用库文件

  • 输入输出流文件iostream{本代码用于输入与输出};
  • 动态数组的向量文件vector{本代码用于比较队列中结点的大小};
  • 队列函数文件queue{本代码用于创建哈夫曼树}~
#include <iostream>
#include <queue>
#include <vector>

 🔯P1:定义结点与指针

struct HuffmanNode {char data;       //定义字符int frequency;   //定义频率HuffmanNode *left, *right;  //定义左指针、右指针HuffmanNode(char data, int frequency) { //初始化this->data = data;this->frequency = frequency;left = right = nullptr;}
};

 🔯P2:用于优先队列中的比较函数

创建结点的步骤在调整<优先队列>时重复出现,因此使用函数封装~

思路:比较指针指向的两个函数,权值高的结点优先度降低。

struct Compare {bool operator()(HuffmanNode* a, HuffmanNode* b) {   //接受两个HuffmanNode对象的指针作为参数,即HuffmanNode* a和HuffmanNode* breturn a->frequency > b->frequency;              //判断a的频率是否大于b的频率,频率高的结点优先度更低}
};

 🔯P3:构造哈夫曼树

传入main函数中的数据动态数组data和频度动态数组frequency~

本步骤的构建过程在博文上面已有图文解释,此处不再赘述~

HuffmanNode* buildHuffmanTree(const vector<char>& data, const vector<int>& frequency) {priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare> pq; //声明了一个优先队列(priority_queue),其中存储的是HuffmanNode类型的对象指针。这个优先队列使用了一个比较函数(Compare)来定义元素的优先级// 创建叶结点并将它们插入优先队列for (int i = 0; i < data.size(); i++) {pq.push(new HuffmanNode(data[i], frequency[i]));}// 构建哈夫曼树while (pq.size() > 1) {HuffmanNode* left = pq.top();   //队首元素记录并出列,即左子结点(left)pq.pop();HuffmanNode* right = pq.top();  //队首元素记录并出列,即右子结点(right)pq.pop();HuffmanNode* newNode = new HuffmanNode('$', left->frequency + right->frequency);    //创建了一个新的HuffmanNode对象,它的频率是左子节点和右子节点的频率之和newNode->left = left;   //在树中链接新节点和左子结点newNode->right = right; //在树中链接新节点和右子结点pq.push(newNode);       //将新的结点插回队列}return pq.top();    //返回根结点
}

 🔯P4:打印哈夫曼树编码

传入树的根结点内存地址,编码由空值开始,以左子树+0,右子树+1的方式遍历树中的结点,如果是叶结点则打印编码~

void printHuffmanCodes(HuffmanNode* root, string code) {if (root == nullptr) {return;}// 如果是叶结点,则打印字符和对应的编码if (!root->left && !root->right) {cout << root->data << " : " << code << endl;}// 递归打印左子树和右子树printHuffmanCodes(root->left, code + "0");printHuffmanCodes(root->right, code + "1");
}

 🔯P5:计算哈夫曼树的权值路径长度(WPL)

传入树的根结点内存地址,树高由0开始(根结点那一行不算权值),先序遍历树中的结点,如果遇到叶子结点则计算权值(频度x权值),并返回加和~

先序遍历的内容可以看这里:🌸[树:双亲、孩子、兄弟表示法][二叉树:先序、中序、后序遍历]

int calculateWPL(HuffmanNode* root, int depth) {if (root == nullptr) {return 0;}// 如果是叶结点,返回权值乘以深度if (!root->left && !root->right) {return root->frequency * depth;}// 递归计算左子树和右子树的WPLint leftWPL = calculateWPL(root->left, depth + 1);int rightWPL = calculateWPL(root->right, depth + 1);return leftWPL + rightWPL;
}

 🔯P6:main函数

main函数除了P0~P5的函数调用,就创建了频度与结点值,以及示意性地增加了结果输出~

int main() {// 示例数据vector<char> data = {'A', 'B', 'C', 'D'};vector<int> frequency = {7, 5, 2, 4};// 构建哈夫曼树HuffmanNode* root = buildHuffmanTree(data, frequency);// 打印哈夫曼树的编码cout << "Huffman Codes:" << endl;printHuffmanCodes(root, "");// 计算并打印哈夫曼树的权值路径长度(WPL)int wpl = calculateWPL(root, 0);cout << "Weighted Path Length (WPL): " << wpl << endl;return 0;
}

🧵完整代码

 🔯P0:完整代码

为了凑本文的字数,我这里贴一下整体的代码,删掉了细部注释~

#include <iostream>
#include <queue>
#include <vector>
using namespace std;// 哈夫曼树的结点定义
struct HuffmanNode {char data;int frequency;HuffmanNode *left, *right;HuffmanNode(char data, int frequency) {this->data = data;this->frequency = frequency;left = right = nullptr;}
};// 用于优先队列中的比较函数
struct Compare {bool operator()(HuffmanNode* a, HuffmanNode* b) {return a->frequency > b->frequency;}
};// 生成哈夫曼树
HuffmanNode* buildHuffmanTree(const vector<char>& data, const vector<int>& frequency) {priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare> pq;for (int i = 0; i < data.size(); i++) {pq.push(new HuffmanNode(data[i], frequency[i]));}while (pq.size() > 1) {HuffmanNode* left = pq.top();pq.pop();HuffmanNode* right = pq.top();pq.pop();HuffmanNode* newNode = new HuffmanNode('$', left->frequency + right->frequency);newNode->left = left;newNode->right = right;pq.push(newNode);}return pq.top();
}// 打印哈夫曼树中的编码
void printHuffmanCodes(HuffmanNode* root, string code) {if (root == nullptr) {return;}if (!root->left && !root->right) {cout << root->data << " : " << code << endl;}printHuffmanCodes(root->left, code + "0");printHuffmanCodes(root->right, code + "1");
}// 计算哈夫曼树的权值路径长度(WPL)
int calculateWPL(HuffmanNode* root, int depth) {if (root == nullptr) {return 0;}if (!root->left && !root->right) {return root->frequency * depth;}int leftWPL = calculateWPL(root->left, depth + 1);int rightWPL = calculateWPL(root->right, depth + 1);return leftWPL + rightWPL;
}int main() {vector<char> data = {'A', 'B', 'C', 'D'};vector<int> frequency = {7, 5, 2, 4};HuffmanNode* root = buildHuffmanTree(data, frequency);cout << "Huffman Codes:" << endl;printHuffmanCodes(root, "");int wpl = calculateWPL(root, 0);cout << "Weighted Path Length (WPL): " << wpl << endl;return 0;
}

 🔯P1:执行结果

运行结果如下图所示~


🔚结语

博文到此结束,写得模糊或者有误之处,欢迎小伙伴留言讨论与批评,督促博主优化内容,不限于以下内容~😶‍🌫️😶‍🌫️

博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下~🌟🌟

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

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

相关文章

form表单使用Select 选择器

案例: ps&#xff1a;年度的值类型要与select 选择器中 value 类型一致&#xff01;&#xff01; 如果input框中显示的是数字&#xff0c;说明年度的值没有与选择器中的的value一致&#xff01;&#xff01;&#xff01; YearNum 要与 value 类型一致&#xff01;&#xff01…

Jmeter的常用设置(一)

文章目录 前言一、Jmeter设置中文 方法一&#xff08;临时改为中文&#xff09;方法二&#xff08;永久改成中文&#xff09;二、启动Jmeter的两种方式 方法一&#xff08;直接启动&#xff0c;不打开cmd窗口&#xff09;方法二&#xff08;带有cmd窗口的启动&#xff09;三、调…

走进Vue2飞入Vue3

✅作者简介&#xff1a;大家好&#xff0c;我是Cisyam&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Cisyam-Shark的博客 &#x1f49e;当前专栏&#xff1a; 前端相关 ✨特色专栏&…

第一代Spring Cloud核心组件

第一代Spring Cloud核心组件&#xff08;Spring Cloud Netflix&#xff09; Eureka服务注册中心(服务注册中心:Eureka,Nacos,Zookeeper,Consul) Ribbon负载均衡 Hystrix熔断器 Feign远程调用组件(Feign RestTemplate Ribbon Hystrix) GateWay网关组件 Config分布式配置中心 …

【C语言】-- 死循环了怎么办?

#include <stdio.h> int main() {int i 0;int arr[] {1,2,3,4,5,6,7,8,9,10};for(i0; i<12; i){arr[i] 0;printf("hello\n");}return 0; } 阅读上面这个代码&#xff0c;我们会认为这不就是简单的数组访问越界么。那么这段代码就应该会报错&#xff0c;…

三维重建以及神经渲染中的学习(三)

三维重建以及神经渲染中的学习 公众号AI知识物语 本文内容为参加过去一次暑期课程学习时的笔记&#xff0c;浅浅记录下。 三维图形可控生成&#xff1a; 1&#xff1a;学习一个图形生成模型 2&#xff1a;具有可控三维变量&#xff1a;1物体形状&#xff1b;2物体位置&…

Ubuntu 放弃了战斗向微软投降

导读这几天看到 Ubuntu 放弃 Unity 和 Mir 开发&#xff0c;转向 Gnome 作为默认桌面环境的新闻&#xff0c;作为一个Linux十几年的老兵和Linux桌面的开发者&#xff0c;内心颇感良多。Ubuntu 做为全世界Linux界的桌面先驱者和创新者&#xff0c;突然宣布放弃自己多年开发的Uni…

七牛云的使用(图片超详讲解)

一、为什么要使用七牛云的OSS(对象存储服务)&#xff1f; 二、七牛云使用&#xff1a; 登录七牛云官网&#xff0c;注册并认证 (初次认证有30天免费使用权限)新建存储空间 点击创建的空间名字&#xff0c;进入 空间概括如下&#xff1a; 阅读帮助文档&#xff0c;在自己的…

Java微服务金融项目智牛股-基础知识三(Restful、HATEOAS、GRPC、SEATA )

Restful定义 Restful是一种软件架构与设计风格&#xff0c; 并非一套标准&#xff0c; 只提供了一些原则与约定条件。REST提供了一组架构约束&#xff0c;当作为一个整体来应⽤用时&#xff0c;强调组件交互的可伸缩性。接⼝口的通⽤用性、组件的独⽴立部署、以及⽤用来减少交…

spring cloud 之 Hystrix

Hystrix概述 Hystrix是一个供分布式系统使用&#xff0c;提供延迟和容错功能&#xff0c;保证复杂的分布系统在面临不可避免的失败是时&#xff0c;仍具有弹性。 当服务器A调用服务器B时&#xff0c;如果服务器B宕机&#xff0c;则服务器A不去调用。当服务器B在时间范围内未响…

基于深度学习的高精度安全帽背心检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度安全帽背心检测识别系统可用于日常生活中或野外来检测与定位安全帽背心目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的安全帽背心目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用…

你知道为什么不用XFP光模块了吗?

在光纤通信应用领域中&#xff0c;10G光模块凭借着较低的成本和功耗被广泛应用于学校、企业等应用场景中。XFP和SFP是10G光模块常见的两种封装类型&#xff0c;那为什么现在市场上XFP光模块应用比较少了呢&#xff1f;下面我们来简单分析一下原因。 一、XFP与SFP光模块的概述 …