数据结构-难点突破(C++实现并查集+路径优化,详解哈夫曼编码树)

news/2024/5/18 20:43:22/文章来源:https://blog.csdn.net/dodamce/article/details/127998377

文章目录

  • 1. 并查集
  • 2. 哈夫曼编码树

1. 并查集

并查集是一个多棵树的集合(森林)。

并查集由多个集合构成,每一个集合就是一颗树。
并:合并多个集合。查:判断两个值是否再一个集合中。

每棵树存在数组中,使用双亲表示法。数组每个元素的父节点。如果没有父节点数组保存-1。根节点位置的数组值就算这颗树节点的个数。
eg:
在这里插入图片描述

根据上图分析可知:
如果要合并的节点x和y不在一个树上
ufs[root_x] += ufs[root_y];(将y这个节点对应的子树和x的节点进行合并)
ufs[root_y] = root_x;(改变y这个节点的父亲节点)

根据上文可知,并查集查找的速度与树的高度有关。

当查找节点后,如果能将这个节点到根节点路径上的节点都直接插入到根节点上,则可以显著降低树的高度,从而提高效率。

在这里插入图片描述

上述过程在查找节点的根节点时实现,找到根节点时先不返回,再遍历一遍更新节点的父节点即可。

把这个节点到根节点路径上的所有节点直接插入到根节点上
while (data != root)
{int parent = ufs[data];ufs[data] = root;data = parent;
}

C++代码:

//构建并查集#include <assert.h>
#include <vector>
#include <stdio.h>class UnionFindSet
{
private://数组的下标保存的是并查集的数据,数组的值记录的是并查集这个节点的父节点下标std::vector<int> ufs;public:UnionFindSet(size_t size){ufs.resize(size, -1);}// x和y所在的两个集合合并void Union(int x, int y){assert(x < ufs.size() && y < ufs.size());int root_x = FindRoot(x);int root_y = FindRoot(y);if (root_x != root_y){//不在一棵树上ufs[root_x] += ufs[root_y];ufs[root_y] = root_x;}}//找data的根int FindRoot(int data){int root = data;while (ufs[root] >= 0){root = ufs[root];}//找到根后,这里做优化,降低并查集树的高度//把这个节点到根节点路径上的所有节点插入到根节点上while (data != root){int parent = ufs[data];ufs[data] = root;data = parent;}return root;}//获取并查集中树的个数int GetTreeSize(){int ret = 0;for (int i = 0; i < ufs.size(); i++){if (ufs[i] < 0)ret += 1;}return ret;}//打印并查集信息void PrintUfs(){for (int i = 0; i < ufs.size(); i++){printf("%2d ", i);}printf("\n");for (int i = 0; i < ufs.size(); i++){printf("%2d ", ufs[i]);}printf("\n");}
};
#include "UnionFindSet.h"
#include <iostream>
int main(int argc, char const *argv[])
{UnionFindSet set(9);for (int times = 0; times < 8; times++){//打印并查集树的个数std::cout << set.GetTreeSize() << std::endl;set.PrintUfs();set.Union(times, times + 1);}std::cout << set.GetTreeSize() << std::endl;set.PrintUfs();return 0;
}

在这里插入图片描述

2. 哈夫曼编码树

哈夫曼树:
在许多应用中,树中结点常常被赋予一个表示某种意义的数值,称为该结点的权。

从树的根到任意结点的路径长度(经过的边数)与该结点上权值的乘积,称为该结点的带权路径长度。

树中所有叶结点的带权路径长度之和称为该树的带权路径长度。

在这里插入图片描述

哈夫曼编码:
哈夫曼编码就是在哈夫曼树的基础上构建的,这种编码方式最大的优点就是用最少的字符包含最多的信息内容,进而实现信息的压缩存储。

根据发送信息的内容,通过统计文本中相同字符的个数作为每个字符的权值,建立哈夫曼树。对于树中的每一个子树,统一规定其左孩子标记为 0 ,右孩子标记为 1 。这样,用到哪个字符时,从哈夫曼树的根结点开始,依次写出经过结点的标记,最终得到的就是该结点的哈夫曼编码。

文本中字符出现的次数越多,在哈夫曼树中的体现就是越接近树根。编码的长度越短。

eg:
在这里插入图片描述

这是用权值分别为 7、5、2、4 的字符 a、b、c、d 构建的哈夫曼树。

显然,字符 a 用到的次数最多,所以它对应的哈弗曼编码应最短,这里用 0 表示;其次,是字符 b 用的多,因此字符 b 编码为 10 ,
以此类推,字符 c 的编码为 110 ,字符 d 的编码为 111。

权值越大,表示此字符在文件中出现的次数越多,那么,为了实现用最少的字符包含最多的内容,就应该给出现次数越多的字符,分配的哈弗曼编码越短。

使用程序求哈夫曼编码有两种方法:

  1. 从叶子结点一直找到根结点,逆向记录途中经过的标记。例如,上图中字符 c 的哈夫曼编码从结点 c 开始一直找到根结点,结果为:0 1 1 ,所以字符 c 的哈夫曼编码为:1 1 0(逆序输出)。
  2. 从根结点出发,一直到叶子结点,记录途中经过的标记。例如,上图中字符 c 的哈夫曼编码,就从根结点开始,依次为:1 1 0。

需要注意:

注意:0和1究竟是表示左子树还是右子树没有明确规定。
左、右孩子结点的顺序是任意的, 所以构造出的哈夫曼树并不唯一,但各哈夫曼树的带权路径长度WPL相同且为最优。
此外,如有若干权值相同的结点,则构造出的哈夫曼树更可能不同,但WPL必然相同且是最优的

哈夫曼树构造流程:
给定N个权值分别为W1, W2,…,Wn”的结点,构造哈夫曼树的算法描述如下:

  1. 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。
  2. 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。
  3. 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。
  4. 重复步骤2和3,直至F中只剩下一棵树为止。

从上述构造过程中可以看岀哈夫曼树具有如下特点:

  1. 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大。
  2. 构造过程中共新建了n-1个结点(双分支结点),因此哈夫曼树的结点总数为2*n-1
  3. 每次构造都选择2棵树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点。

C++代码:

#include <iostream>#include <unordered_map>#include <string>#include <vector>#include <assert.h>
//传入一串字符,使用哈夫曼树对其进行编码,字符串中每个字符出现的次数就是作为哈夫曼树的权值struct TreeNode
{char val;   //节点保存的值int weight; //权值TreeNode *left;TreeNode *right;TreeNode(int _weight) : val(0), weight(_weight), left(nullptr), right(nullptr) {}TreeNode(char _val, int _weight) : val(_val), weight(_weight), left(nullptr), right(nullptr) {}
};class HuffTree
{
private:void _InitTimes(const std::string &src){for (int i = 0; i < src.size(); i++){times[src[i]] += 1;}}void _PreDisplay(TreeNode *node, std::string &str){if (node->left == nullptr && node->right == nullptr){//叶子节点// std::cout << str << std::endl;code[node->val] = str;return;}if (node->left != nullptr){str.push_back('0');_PreDisplay(node->left, str);str.pop_back(); //出递归后,刚刚递归进去给字符串插入的字符也要弹出}if (node->right != nullptr){str.push_back('1');_PreDisplay(node->right, str);str.pop_back();}}void _PreDisplay(TreeNode *node){std::string retBuff;_PreDisplay(node, retBuff);}public:TreeNode *root;                             //构造的哈夫曼树std::unordered_map<char, std::string> code; //保存字符进行编码的结果std::unordered_map<char, int> times;        //保存传入的字符串中每个字符出现的次数HuffTree(const std::string &src){//统计每个字符出现的次数_InitTimes(src);std::vector<TreeNode *> ArrayNode;//先给所有节点开辟空间std::unordered_map<char, int>::iterator pos = times.begin();while (pos != times.end()){ArrayNode.push_back(new TreeNode(pos->first, pos->second));pos++;}//循环创建哈夫曼树节点//如果只有一个节点if (ArrayNode.size() == 0){root = ArrayNode[0];}else{for (int time = 0; time < ArrayNode.size() - 1; time++){//找权值最小的和第二小的节点int minIndex = 0;int minSecIndex = 0;// ArrayNode[minIndex] == nullptr证明这个节点已经建立过哈夫曼树了,需要跳过while (ArrayNode[minIndex] == nullptr){minIndex++;}for (int i = 0; i < ArrayNode.size(); i++){if (ArrayNode[i] != nullptr && ArrayNode[i]->weight < ArrayNode[minIndex]->weight){minIndex = i;}}//找次小值while (ArrayNode[minSecIndex] == nullptr || minIndex == minSecIndex){minSecIndex++;}for (int i = 0; i < ArrayNode.size(); i++){if (i != minIndex){if (ArrayNode[i] != nullptr && ArrayNode[i]->weight < ArrayNode[minSecIndex]->weight){minSecIndex = i;}}}// printf("出现次数最小的字符是%c,出现次数%d\n", ArrayNode[minIndex]->val, ArrayNode[minIndex]->weight);// printf("出现次数次少的字符是%c,出现次数为%d\n", ArrayNode[minSecIndex]->val, ArrayNode[minSecIndex]->weight);// printf("============\n");//创建新节点,将这个节点插入到最小字符位置,次少节点位置处理过了置空.并将树结构构造好root = new TreeNode(ArrayNode[minIndex]->weight + ArrayNode[minSecIndex]->weight);root->left = ArrayNode[minIndex];root->right = ArrayNode[minSecIndex];ArrayNode[minIndex] = root;ArrayNode[minSecIndex] = nullptr;}}}//打印编码void PrintCode(){//遍历树,直到遍历到叶子节点,规定向左树走编码为0,向右树走编码为1_PreDisplay(root);//打印编码集合auto pos = code.begin();while (pos != code.end()){std::cout << pos->first << ":" << pos->second << std::endl;pos++;}}//解码char DeCode(const std::string &src){TreeNode *node = root;for (int i = 0; i < src.size(); i++){if (src[i] == '0')node = node->left;if (src[i] == '1')node = node->right;}assert(node->left == nullptr && node->right == nullptr); // node一定走到了叶子节点return node->val;}
};
#include "hufftree.h"int main(int argc, char const *argv[])
{HuffTree tree("abandon");tree.PrintCode();std::cout << "010解码:" << tree.DeCode("010") << std::endl;return 0;
}

这里构建的哈夫曼树如下图:
abandon中
a出现2次,b出现1次,n出现2次,d出现1次,o出现1次。次数作为整个节点的权值

在这里插入图片描述

在这里插入图片描述
需要注意的是,这里对其编码使用一个字符‘1’或‘0’进行编码,压缩效果不明显。

正式做项目时,可以选择树向左移动时,代表比特位0,向右移动时代表比特1。这样才可以真正达到压缩效果。

具体细节可以移步数据结构-压缩软件核心-C++(利用哈夫曼树进行编码,对文件进行压缩与解压缩)

代码位置Github

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

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

相关文章

集世界杯+GameFi元素的MetaElfLand,为何将在世界杯期间爆发?

又到了四年一度的球迷狂欢节&#xff0c;本次卡塔尔世界杯已于11月21号举行。 每当世界杯来临&#xff0c;与世界杯相关产业都会迎来一波爆发&#xff0c;毕竟这个千亿美金市值的市场暗藏着无数的机会。而自GameFi的火热开始&#xff0c;世界杯也成为了加密投资者的狂欢日&…

pytorch的buffer学习整理

pytorch模型中的buffer 这段时间忙于做项目&#xff0c;但是在项目中一直在模型构建中遇到buffer数据&#xff0c;所以花点时间整理下模型中的parameter和buffer数据的区别&#x1f495; 1.torch.nn.Module.named_buffers(prefix‘‘, recurseTrue) 贴上pytorch官网对其的说…

分布式文件系统HDFS实践及原理详解part3

HDFS原理 说明&#xff1a;3.5开头目录是因为和上篇文章内容同属一章&#xff0c;所以开头使用了3.5 3.5 HDFS核心设计 3.5.1 心跳机制 1、 Hadoop 是 Master/Slave 结构&#xff0c;Master 中有 NameNode 和 ResourceManager&#xff0c;Slave 中有 Datanode 和 NodeManag…

异构网络小入

A Survey of Heterogeneous Information Network Analysis Heterogeneous Graph Attention Network 异构网络很火吗&#xff1f; 在一个网络中&#xff0c;不用节点的类型不同&#xff0c;这是肯定的。 所以&#xff0c;异构网络在表征比较复杂的情形时&#xff0c;是比较合适…

基于图像识别的小车智能寻迹控制系统

目录 摘要…… I Abstract II 基于图像识别的智能寻迹控制系统设计 I Design of Intelligent tracking Control system based on Image recognition II 目录 III 第1章 绪论 1 1.1 课题背景 1 1.1 国内外文献综述 1 1.2 论文研究内容 2 第2章 基于图像识别的智能寻迹控制系统方…

【安装Ubuntu18.04遇到的问题】未找到WIFI适配器

大家好&#xff0c;我是小政。好久没有更新文章&#xff0c;近期开始陆续分享一些研究生阶段正在学习的知识和遇到的一些问题。 联想拯救者Y9000P关于安装Ubuntu未找到WIFI适配器的解决方法1.Ubuntu18.042.网卡信息3.解决方法&#xff08;1&#xff09;用手机USB连接电脑提供网…

动态规划--树型dp

6个题1. 树的最长路径2.树的中点.由于第三题需要用到一些数学地知识&#xff0c;所以先去补一补数学知识。连接链接在这里4.二叉苹果树5.战略游戏6.皇宫守卫1. 树的最长路径 定义&#xff1a;树中两个点直接的最远距离称为树的直径 先说一个结论 先任意找到一个树中一个点u&am…

分布式协调系统ZooKeeper实践与原理剖析

基础的一些知识&#xff0c;高阶知识后续看看补充 第一章 ZooKeeper概述 1.1 介绍 What is ZooKeeper&#xff1f; Apache ZooKeeper is an effort to develop and maintain an open-source server which enables highly reliable distributed coordination ZooKeeper is…

大学生静态HTML网页设计--公司官网首页

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 公司官网网站 | 企业官网 | 酒店官网 | 等网站的设计与制 HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&#xf…

SpringIoc依赖查找-5

1. 依赖查找的今世前生: Spring IoC容器从Java标准中学到了什么? 单一类型依赖查找 JNDI - javax.naming.Context#lookup(javax.naming.Name) JavaBeans - java.beans.beancontext.BeanContext 集合类型依赖查找 java.beans.beancontext.BeanContext 集合查找方法 层…

sqli-labs/Less-51

这一关的欢迎界面依然是以sort作为注入点 我们首先来判断一下是否为数字型注入 输入如下 sortrand() 对尝试几次 发现页面并没有发生变化 说明这道题的注入类型属于字符型 然后尝试输入以下内容 sort1 报错了 报错信息如下 我们从报错信息可以知道这道题的注入类型属于单…

期末前端web大作业——HTML+CSS+JavaScript仿京东购物商城网页制作(7页)

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

#边学边考 必修5 高项:对人管理 第2章 项目沟通管理和干系人管理

答题报告 自我分析 有可能是间隔时间太长&#xff0c;本章节从开始学习到今天&#xff08;11.24&#xff09;学完&#xff0c;中间至少停止了1周以上&#xff0c;造成对基本知识记忆不牢固。对重点知识没有重点记忆&#xff0c;走马观花&#xff0c;以至于混淆。 答题解析 关…

MySQL 进阶 图文详解InnoDB储存引擎

前言 SQL 语句的最终执行者是存储引擎。存储引擎在经解析器、优化器处理后被执行器调用其接口执行优化后的执行计划。MySQL 存储引擎包括 InnoDB、Myisam、Memory、Archive、CSV 存储引擎等&#xff0c;其中最常用也是MySQL 默认的存储引擎是 InnoDB。 写入缓冲池&#xff08;…

用DIV+CSS技术设计的水果介绍网站(web前端网页制作课作业)

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

软件测试面试技巧有哪些?可以从这2个方面去进行准备

面试所有只职场人&#xff0c;通往工作岗位的第一道关卡&#xff0c;也是最重要的一道门槛。而面试中&#xff0c;如何回答HR提出的问题很大程度上决定了面试能不能成功。所以这些软件测试的面试技巧你可不能错过了。 首先是自我介绍 自我介绍的时间不能太短&#xff0c;几十秒…

(附源码)计算机毕业设计JavaJava毕设项目财务管理系统的设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven Vue 等等组成&#xff0c;B/…

【Flutter】shape 属性 ShapeBorder,形状

文章目录前言一、shape 是什么&#xff1f;二、不同的形状1.BeveledRectangleBorder2.Border3.CircleBorder圆形4.ContinuousRectangleBorder连续圆角5.StadiumBorder 体育场边界 &#xff0c;药丸形状6.OutlineInputBorder外边框可以定制圆角7.UnderlineInputBorder下划线总结…

Springboot Security 前后端分离模式自由接口最小工作模型

但凡讲解Springboot Security的教程&#xff0c;都是根据其本身的定义&#xff0c;前后端整合在一起&#xff0c;登录采用form或者basic。我们现在的很多项目&#xff0c;前后端分离&#xff0c;form登录已经不适用了。很多程序的架构要求所有的接口都采用application/json方式…

复制集群架构设计技巧

Redis Sentinel设计技巧 Redis Sentinel基本架构 Monitoring Sentinel可以监控Redis节点的状态 Notification Sentinel可以通过API进行集群状态通知 Automatic failover Sentinel实现故障自动切换 Configuration provider Sentinel为client提供发现master节点的发现功能…