数据结构 -- 并查集与图

news2025/1/23 15:05:17

目录

1.并查集

1.结构

2.原理

3.代码实现

1.存储

2.寻找根节点

3.是否为同一集合

4.求集合个数

5.合并为同一集合中

整体代码

2.图

1.基本知识

1.各个属性

2.特殊名词

3.图的解释

2.图的表示

1.邻接矩阵

2.邻接表

3.图的遍历

1.BFS--广度优先遍历

2.DFS--深度优先遍历


1.并查集

定义:n个元素被划分在不相交的集合中,一旦出现某几个元素可以形成一个相同的集合内,那么此时就会被该结构放入同一个集合内.该集合的好处就是当需要查询就能得到某个元素是属于哪个集合中.

1.结构

其实无非就是用于记载是否存在在哪个集合中,那么只需要有一个能存储某个位置对应的集合,而位置是唯一的,那么每一个位置其实就已经代表特定的元素.

1.为了表示存储元素的位置,那么就需要一个vector

2.每个位置对应的元素,那么只需要hash表即可表示特定的元素的特定下标

2.原理

1.其实我们可以把并查集表示为森林

2.如果下标对应的元素为正数,则表示和元素对应下标的元素为同一集合;如果下标对应的元素为负数,表示当前为森林的顶端

3.如果我们已经知道每个下标代表的意义,那么不需要哈希表进行表示.如果需要对下标进行解析,可以使用hash对元素和下标进行映射

3.代码实现

1.存储

class UnionFindSet
{
private:
    vector<int> _ufs;
public:
    UnionFindSet(size_t n)
	    :_ufs(n, -1)
    {}
}

1.先设置一个vector

2.构造时,需要多少元素就设置多少,并且初始化为-1,表示当前的元素独立

2.寻找根节点

int FindRoot(int x)
{
	int root = x;
	while (_ufs[root] >= 0)
		root = _ufs[root];
	while (_ufs[x] >= 0)
	{
		int parent = _ufs[x];
		_ufs[x] = root;
		x = parent;
	}
	return root;
}

我们知道循着元素下标往上找就能找到我们需要的根节点并且实现压缩数据

3.是否为同一集合

bool InSet(int x1, int x2)
{
	return FindRoot(x1)== FindRoot(x2);
}

对比二者的根节点是否相同即可

4.求集合个数

size_t SetSize()
{
	size_t size = 0;
	for (size_t i = 0; i < _ufs.size(); i++)
	{
		if (_ufs[i] < 0) size++;
	}
	return size;
}

只需要根节点就可以了,根节点的特点就是元素为负数,那么只需要遍历vector,计算负数的个数就可以了.

5.合并为同一集合中

void Uinon(int x1, int x2)
{
	int root1 = FindRoot(x1);
	int root2 = FindRoot(x2);
	if (root1 == root2) return;
	if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2);
	_ufs[root1] += _ufs[root2];
	_ufs[root2] = root1;
}

1.找到两个元素对应的根节点,如果根节点相同则不需要结合

2.如果不相同,我们需要将数据量小的合并到数据量大的去

3.那么当前root1就是小的下标._ufs[root1] += _ufs[root2]表示当前的root1为根节点时的所有元素个数,_ufs[root2] = root1将root2的下标记作root1

整体代码

class UnionFindSet
{
private:
	vector<int> _ufs;
public:
	UnionFindSet(size_t n)
		:_ufs(n, -1)
	{}

	void Uinon(int x1, int x2)
	{
		int root1 = FindRoot(x1);
		int root2 = FindRoot(x2);
		if (root1 == root2) return;
		if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2);
		_ufs[root1] += _ufs[root2];
		_ufs[root2] = root1;
	}

	int FindRoot(int x)
	{
		int root = x;
		while (_ufs[root] >= 0)
			root = _ufs[root];
		while (_ufs[x] >= 0)
		{
			int parent = _ufs[x];
			_ufs[x] = root;
			x = parent;
		}
		return root;
	}

	bool InSet(int x1, int x2)
	{
		return FindRoot(x1)== FindRoot(x2);
	}

	size_t SetSize()
	{
		size_t size = 0;
		for (size_t i = 0; i < _ufs.size(); i++)
		{
			if (_ufs[i] < 0) size++;
		}
		return size;
	}
};

2.图

1.基本知识

1.各个属性

图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V,E)

1.顶点集合:V = {x|x属于某个数据对象集}是有穷非空集合
2.边集合:E = {(x,y)|x,y属于V}或者E={<x, y>|x,y属于V&&Path(x, y)}是顶点间关系的有穷集合.

3.图的种类可分为:有向图和无向图

2.特殊名词

1.完全图:每一个顶点都互相有一个边联通的叫完全图。[其中在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此图为无向完全图;在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图]

2.邻接顶点:两个顶点通过一个边可互相抵达的。[在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;在有向图G中,若<u, v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边<u, v>与顶点u和顶点v相关联]

3.顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v)。[在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)]

4.路径:在图G = (V, E)中,两顶点可经过若干边抵达的,就叫路劲。若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从顶点vi到顶点vj的路径。

5.路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和

3.图的解释

1.简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路径;若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环。
2.子图:设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图
3.连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图

4.强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图

5.生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边。

2.图的表示

1.邻接矩阵

1.适合存储非常稠密的图

2.邻接矩阵可以O(1)判断两个顶点之间的连接关系

3.一个点的所有边不方便找

namespace Matrix
{
	template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
	class Graph
	{
	public:
		//图的创建 -- 1.IO  2.关系放在文件中  3.手动添加边
		Graph(const V* a, size_t n)
		{
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				_vertexs.push_back(a[i]);
				_indexMap[a[i]] = i;
			}
			_matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
					_matrix.resize(n, MAX_W);
				}
			}
		}

		size_t GetVertexIndex(const V& v)
		{
			auto it = _indexMap.find(v);
			if (it == _indexMap.end())
			{
				throw invalid_argument("顶点不存在");
				return -1;
			}
			else
			{
				return it->second;
			}
		}

		void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			_matrix[srci][dsti] = w;
			if (Direction == false)
			{
				_matrix[dsti][srci] = w;
			}
		}

		void Print()
		{
			// 打印顶点和下标映射关系
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << _vertexs[i] << "-" << i << " ";
			}
			cout << endl;

			cout << "  ";
			for (size_t i = 0; i < _matrix.size(); i++)
			{
				cout << i << " ";
				for (size_t j = 0; j < _matrix[0].size(); j++)
				{
					if (_matrix[i][j] == MAX_W)
						cout << "* ";
					else
						cout << _matrix[i][j] << " ";
				}
				cout << endl;
			}
			cout << endl;
		}

	private:
		vector<V> _vertexs; //保存顶点
		map<V, int> _indexMap; //顶点与下标映射
		vector<vector<W>> _matrix; //邻接矩阵
	};
}

2.邻接表

1.适合比较稀疏的图

2.适合找到一个顶点的所有边

3.不适合判断两个顶点之间的连接关系

namespace link_table
{
	template<class W>
	struct Edge
	{
		int _dsti; //目标点下标
		W _w; //权值
		Edge<W>* _next;
		Edge(int dsti,const int w)
			:_dsti(dsti),
			_w(w),
			_next(nullptr)
		{}
	};

	template<class V, class W, bool Direction = false>
	class Graph
	{
		typedef Edge<W> Edge;
	public:
		Graph(const V* a, size_t n)
		{
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				_vertexs.push_back(a[i]);
				_indexMap[a[i]] = i;
			}

			_table.resize(n, nullptr);
		}

		size_t GetVertexIndex(const V& v)
		{
			auto it = _indexMap.find(v);
			if (it == _indexMap.end())
			{
				throw invalid_argument("顶点不存在");
				return -1;
			}
			else
			{
				return it->second;
			}
		}

		void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			//srci->dsti
			Edge* eg = new Edge(dsti, w);
			eg->_next = _table[srci];
			_table[srci] = eg;

			//无向图 dsti->srci
			if (Direction == false)
			{
				Edge* eg = new Edge(srci, w);
				eg->_next = _table[dsti];
				_table[dsti] = eg;
			}
		}

		void Print()
		{
			// 打印顶点和下标映射关系
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << _vertexs[i] << "-" << i << " ";
			}
			cout << endl;

			for (size_t i = 0; i < _table.size(); i++)
			{
				cout << _vertexs[i] << "[" << i << "]->";
				Edge* cur = _table[i];
				while (cur)
				{
					cout << "{" << _vertexs[cur->_dsti] << "[" << cur->_dsti << "]" << cur->_w << "}->";
					cur = cur->_next;
				}
				cout << "{nullptr}" << endl;
			}
		}
	private:
		vector<V> _vertexs; //保存顶点
		map<V, int> _indexMap; //顶点与下标映射
		vector<Edge*> _table; //邻接表
	};
}

3.图的遍历

1.BFS--广度优先遍历

1.bfs的实现其实就是基于队列实现的

2.先将第一个顶点push到队列中,那么我们就可以基于该顶点进行遍历.需要注意的是,我们需要一个表来查看走到的点是否已经遍历过了

3.之后的循环,每次都查看每一层的顶点,并且将下一层的相邻顶点连接.遍历过的点pop掉并且将其访问的点进行标记已经访问

		void BFS(const V& src)
		{
			size_t srci = GetVertexIndex(src);

			// 队列和标记数组
			queue<int> q;
			vector<bool> visited(_vertexs.size(), false);

			q.push(srci);
			visited[srci] = true;
			int levelSize = 1;

			size_t n = _vertexs.size();
			while (!q.empty())
			{
				// 一层一层出
				for (int i = 0; i < levelSize; ++i)
				{
					int front = q.front();
					q.pop();
					cout << front << ":" << _vertexs[front] << " ";
					// 把front顶点的邻接顶点入队列
					for (size_t i = 0; i < n; ++i)
					{
						if (_matrix[front][i] != MAX_W)
						{
							if (visited[i] == false)
							{
								q.push(i);
								visited[i] = true;
							}
						}
					}
				}
				cout << endl;

				levelSize = q.size();
			}

			cout << endl;
		}

2.DFS--深度优先遍历

将当前访问的点进行遍历,随后标记为访问过了,循环该点的其他相邻点,同样的逻辑

		void _DFS(size_t srci, vector<bool>& visited)
		{
			cout << srci << ":" << _vertexs[srci] << endl;
			visited[srci] = true;

			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				if (_matrix[srci][i] != MAX_W && visited[i] == false)
				{
					_DFS(i, visited);
				}
			}

		}

		void DFS(const V& src)
		{
			size_t srci = GetVertexIndex(src);
			vector<bool> visited(_vertexs.size(), false);

			_DFS(srci, visited);
		}

这样的代码其实有弊端,就是只能遍历相邻的点.一旦该图不是连通图就会出现只能访问一部分.那么就需要在DFS处遍历每一个点,如果没有被_DFS遍历标记过就需要再一次进行_DFS遍历

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

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

相关文章

Python with提前退出:坑与解决方案

Python with提前退出&#xff1a;坑与解决方案 问题的起源 早些时候使用with实现了一版全局进程锁&#xff0c;希望实现以下效果&#xff1a; Python with提前退出&#xff1a;坑与解决方案 全局进程锁本身不用多说&#xff0c;大部分都依靠外部的缓存来实现的&#xff0c;r…

python读取excel自动化生成sql建表语句和java实体类字段

1、首先准备一个excel文件&#xff1a; idtypenameidint学号namestring姓名ageint年龄sexstring性别weightdecimal(20,4)体重scoredecimal(20,4)分数 2、直接生成java字段和注释&#xff1a; import pandas as pddf pd.read_excel(test.xlsx, sheet_nameSheet1)for i in ran…

k8s中批量处理Pod应用的Job和CronJob控制器介绍

目录 一.Job控制器 1.简介 2.Jobs较完整解释 3.示例演示 4.注意&#xff1a;如上例的话&#xff0c;执行“kubectl delete -f myJob.yaml”就可以将job删掉 二.CronJob&#xff08;简写为cj&#xff09; 1.简介 2.CronJob较完整解释 3.案例演示 4.如上例的话&#xf…

【MySQL】常用内置函数:数值函数 / 字符串函数 / 日期函数 / 其他函数

文章目录 数值函数round()&#xff1a;四舍五入ceiling()&#xff1a;上限函数floor()&#xff1a;地板函数abs()&#xff1a;计算绝对值rand()&#xff1a;生成0-1的随机浮点数 字符串函数length()&#xff1a;获取字符串中的字符数upper() / lower()&#xff1a;将字符串转化…

使用Ray创建高效的深度学习数据管道

大家好&#xff0c;用于训练深度学习模型的GPU功能强大但价格昂贵。为了有效利用GPU&#xff0c;开发者需要一个高效的数据管道&#xff0c;以便在GPU准备好计算下一个训练步骤时尽快将数据传输到GPU&#xff0c;使用Ray可以大大提高数据管道的效率。 1.训练数据管道的结构 首…

优化邮件群发效果的方法与策略

怎样优化邮件群发效果&#xff1f;这是许多企业在进行邮件营销时常常被问到的问题。邮件营销是一种高效且经济实惠的市场推广方式&#xff0c;但如何使邮件真正引起接收者的兴趣并产生预期的效果并不容易。好的营销效果可以带来高回报、高收益率&#xff0c;但是怎么提升群发效…

工会排队奖励模式:创新营销策略,实现共赢局面

在当今的商业环境中&#xff0c;创新营销策略的重要性日益凸显。工会排队奖励模式作为一种新型的营销策略&#xff0c;旨在通过结合线上和线下消费&#xff0c;激励消费者购买产品或服务&#xff0c;并获得返现奖励。这种模式通过将消费者的支出和商家的抽成资金纳入奖金池&…

CSS3样式详解之圆角、阴影及变形

目录 前言一、圆角样式&#xff08;border-radius&#xff09;二、元素阴影&#xff08;box-shadow&#xff09;三、过渡动画样式&#xff08;transition&#xff09;1. transition-property(用于设置属性名称)2. transition-duration&#xff08;设置时间&#xff09;3. trans…

7、信息收集(2)

文章目录 一、指纹识别1、Nmap工具2、Wafw00f工具 二、使用Maltego进行情报收集 一、指纹识别 1、Nmap工具 命令一&#xff1a;nmap -sS -sV <ip>&#xff0c;使用TCP SYN的方式&#xff0c;扫描目标主机上常规端口运行的服务版本。 -sS&#xff1a;指定使用TCP SYN的方…

注解(概念、分类、自定义注解)

注解基本概念 注解(元数据)为我们在代码中添加信息提供一种形式化的方法&#xff0c;我们可以在某个时刻非常方便的使用这些数据。将的通俗一点&#xff0c;就是为这个方法增加的说明或功能。 作用&#xff1a; 编写文档&#xff1a;通过代码里标识的注解生成文档【生成doc文…

常使用的定时任务

常使用的定时任务 一、 linux自带的定时任务 1、crontab 有这样一个需求&#xff1a;我们使用Java写一个工具jar包在系统空闲的时候去采集已经部署在Linux系统上的项目的一 些数据&#xff0c;可以使用 linux 系统的 crontab。 运行crontab -e&#xff0c;可以编辑定时器&…

git stash save untracked not staged

git stash save untracked not staged 如图 解决方案&#xff1a; git stash save "tag标记信息" --include-untracked或者&#xff1a; git stash save -u "tag标记信息" git stash clear清空本地暂存代码_zhangphil的博客-CSDN博客文章浏览阅读486次。…

合阔智云:实现API无代码开发,连接ERP系统和CRM系统提高运营效率

概述 合阔智云&#xff0c;一家成立于2011年的科技公司&#xff0c;核心业务是提供云原生和移动化设计的新一代全渠道“云端一体”履约中台和去中心化模式智能门店供应链业务中台。他们的系统可以无需API开发即可实现电商系统和客服系统的连接和集成&#xff0c;大大提高了企业…

通过python脚本上传本地/远程服务器文件到minio

前言 将文件上传到MinIO对象存储后&#xff0c;MinIO会将文件存储为对象(.meta文件)&#xff0c;并为每个对象生成相应的元数据。元数据是描述对象的属性和信息的数据。 通常&#xff0c;元数据包括对象的名称、大小、创建日期等。 在MinIO中&#xff0c;对象的元数据存储在独立…

振南技术干货集:各大平台串口调试软件大赏(1)

注解目录 &#xff08;串口的重要性不言而喻。为什么很多平台把串口称为 tty&#xff0c;比如 Linux、MacOS 等等&#xff0c;振南告诉你。&#xff09; 1、各平台上的串口调试软件 1.1Windows 1.1.1 STCISP &#xff08;感谢 STC 姚老板设计出 STCISP 这个软件。&#xf…

FPGA程序执行相关知识点

1.目前&#xff0c;大多数FPGA芯片是基于 SRAM 的结构的&#xff0c; 而 SRAM 单元中的数据掉电就会丢失&#xff0c;因此系统上电后&#xff0c;必须要由配置电路将正确的配置数据加载到 SRAM 中&#xff0c;此后 FPGA 才能够正常的运行。 常见的配置芯片有EPCS 芯片 &#x…

基于傅里叶变换的运动模糊图像恢复算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、傅里叶变换与图像恢复 4.2、基于傅里叶变换的运动模糊图像恢复算法原理 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 %获取角度 img…

[密码学]DES

先声明两个基本概念 代换&#xff08;substitution&#xff09;,用别的元素代替当前元素。des的s-box遵循这一设计。 abc-->def 置换&#xff08;permutation&#xff09;&#xff0c;只改变元素的排列顺序。des的p-box遵循这一设计。 abc-->bac DES最核心的算法就是…

从零搭建AlibabaCloud微服务项目

1&#xff0c;创建maven项目工程如下 equipment-admin 后台equipment-applet 前台或小程序端或app、h5equipment-common 公共模块equipment-gateway 网关equipment-mapper mapper层操作数据库equipment-model 实体类对应数据库表 2&#xff0c;在父pom文件引入依赖 <proper…

找不到 sun.misc.BASE64Decoder ,sun.misc.BASE64Encoder 类

找不到 sun.misc.BASE64Decoder &#xff0c;sun.misc.BASE64Encoder 类 1. 现象 idea 引用报错 找不到对应的包 import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder;2. 原因 因为sun.misc.BASE64Decoder和sun.misc.BASE64Encoder是Java的内部API&#xff0c;通…