最小生成树与最短路径

news2024/12/24 0:00:04

目录

一.最小生成树

1.1概念

 1.2Kruskal算法

 1.3Prim算法

 二.最短路径

2.11单源最短路径--Dijkstra算法

2.1.2单源最短路径--Bellman-Ford算法


一.最小生成树

1.1概念

连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树 就不在连通;反之,在其中引入任何一条新边,都会形成一条回路。

若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。

1. 只能使用图中的边来构造最小生成树

2. 只能使用恰好n-1条边来连接图中的n个顶点

3. 选用的n-1条边不能构成回路

 1.2Kruskal算法

思路:先构造n个顶点的,不含边的图(G={V,NULL}),后面不断从边的集合中选出权值最小的那一条边,并且加入该边后不会形成回路。

核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树

例如:

步骤:

1.先构造n个顶点,无边的图。

2.将原来图的边存储好,放入一个可排序的容器中,例如priority_queue,将边按权值的大小排好序。

3.后面选出权值最小的边时,将其加入图中,此时要知道该边的两个顶点与权值。所以priority_queue存储的是边的权值以及该边两边的顶点信息,可用一个结构体来表示。

4.每次选出权值最小的边时,将其加入图中时,要判断加入的边是否会形成回路,可用并查集来判断。

5.选出n-1条边,且未形成回路,即是最小生成树

代码实现:

 struct Edge   //优先级队列存储的元素
	{
		int _dsti;  //起始点下标
		int  _srci;// 目标点的下标
		W _w;		// 权值

		Edge(int srci,int dsti, const W w)
			:_dsti(dsti)
			, _srci(srci)
			, _w(w)
		{}
		bool operator>(const Edge& e) const //从小到大排列
		{
			return _w > e._w;
		}
	};
	typedef Grap<V, W, W_MAX, Direc> Self;
	W Kruskal(Self minTree)
	{
		int n = _vertexs.size();
		for (size_t i = 0; i < n; ++i)
		{
			minTree._matrix[i].resize(n, W_MAX);
		}
		priority_queue<Edge, vector<Edge>, greater<Edge>> _q; //先将边存储到的队列排好序
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (_matrix[i][j] != 0)
					_q.push(Edge(i, j, _matrix[i][j]));
			}
		}
		cout << "开始选边" << endl;
		UnionFindSet u(n);  //并查集
		int k = 0;
		W sum=W();
		while (!_q.empty())
		{
			Edge e = _q.top();
			_q.pop();
			if (!u.InSet(e._dsti, e._srci)) //若位形成回路
			{

				cout << "选出" << _vertexs[e._dsti]<<" " <<_vertexs[e._srci]<<" "<< e._w << endl;
				u.Union(e._dsti, e._srci);
				sum += e._w;
				minTree.AddEdge(_vertexs[e._dsti], _vertexs[e._srci], e._w);
				k++;
			}
		}
		if (k = n - 1)
		{
			return sum;
		}
		else
		{
			cout << "存在回路" << endl;
		}
	}

结果:创建的是上面的图

void test1()
{
	const char* str = "abcdefghi";
	Grap<char, int> g(str, strlen(str));
	g.AddEdge('a', 'b', 4);
	g.AddEdge('a', 'h', 8);
	g.AddEdge('a', 'h', 9);
	g.AddEdge('b', 'c', 8);
	g.AddEdge('b', 'h', 11);
	g.AddEdge('c', 'i', 2);
	g.AddEdge('c', 'f', 4);
	g.AddEdge('c', 'd', 7);
	g.AddEdge('d', 'f', 14);
	g.AddEdge('d', 'e', 9);
	g.AddEdge('e', 'f', 10);
	g.AddEdge('f', 'g', 2);
	g.AddEdge('g', 'h', 1);
	g.AddEdge('g', 'i', 6);
	g.AddEdge('h', 'i', 7);
	g.Print();
	cout << g.Kruskal(g) << endl;

}

 1.3Prim算法

思路:从任意一个顶点开始,选出与其相连且权值最小的边,后面已经遍历过的顶点进行同样的操作,直到选完n-1条边。(选过的边不在遍历,该算法是贪心思想)

例如:假如从顶点A开始选边

 步骤

1.先构造n个顶点,无边的图。

2.从给出的顶点(a)开始,将其与其相连的边放入优先级队列中排序,选出权值最小的边。同时记录该边对应的另一个顶点(b)。(此时顶点a已访问过,在添加边时,后面选出的边中对应的另一个顶点不能是a,在将对应顶点相连的边加入队列时,选出的边中顶点也必须是未访问过的,才能不形成回路)

3.对顶点b进行上面步骤2操作

3.选出n-1条边,且未形成回路,即是最小生成树。

代码:

  W Prim(Self minTree, const W& src)
{
	size_t srci = get_index(src);
	size_t n = _vertexs.size();
	minTree._matrix.resize(n);
	for (size_t i = 0; i < n; ++i)
	{
		minTree._matrix[i].resize(n, 0);
	}
	vector<bool> X(n, true);
	vector<bool> Y(n, true);
	X[srci] = false; //从X集合中选出到集合Y的最短边
	Y[srci] = false;

	priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
	for (size_t i = 0; i < n; ++i)// 先把起始点连接的边添加到队列中
	{
		if (_matrix[srci][i] != 0)
		{
			minq.push(Edge(srci, i, _matrix[srci][i]));
		}
	}

	cout << "开始选边" << endl;
	size_t size = 0;
	W totalW = W();
	while (!minq.empty())
	{
		Edge min = minq.top();
		minq.pop();

		// 最小边的目标点也在X集合,则构成环,不要添加
		if (!X[min._dsti])
		{
			cout << "构成环:";
			cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
		}
		else
		{
			minTree._AddEdge(min._srci, min._dsti, min._w);
			cout << "选边" << " " << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
			X[min._dsti] = false;
			Y[min._dsti] = false;
			++size;
			totalW += min._w;
			if (size == n - 1)
				break;

			for (size_t i = 0; i < n; ++i)
			{
				if (_matrix[min._dsti][i] != 0 && Y[i])// 顶点i是未访问过的
				{
					minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
				}
			}
		}
	}
	if (size == n - 1)
	{
		return totalW;
	}
	else
	{
		return W();
	}
}

结果:

 二.最短路径

最短路径问题:从在带权有向图 G 中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

2.11单源最短路径--Dijkstra算法

介绍:Dijkstra算法就适用于解决带权重的有向图上的单源最短 路径问题,同时算法要求图中所有边的权重非负。一般在求解最短路径的时候都是已知一个起点 和一个终点,所以使用Dijkstra算法求解过后也就得到了所需起点到终点的最短路径。

思路:

针对一个带权有向图 G ,将所有结点分为两组 S Q S 是已经确定最短路径的结点集合,在初始时为空(初始时就可以将源节点s 放入,毕竟源节点到自己的代价是 0 ), Q 为其余未确定最短路径的结点集合,每次从 Q 中找出一个起点到该结点代价最小的结点 u ,将 u Q 中移出,并放入 S 中,对 u 的每一个相邻结点 v 进行松弛操作 。松弛即对每一个相邻结点 v ,判断源节点 s 到结点 u的代价与u v 的代价之和是否比原来 s v 的代价更小,若代价比原来小则要将 s v 的代价更新为s u u v 的代价之和,否则维持原样。
例如:一下面这幅图为例

 

 上面的两个数组中,一个来表示s->到目标点的最小权值,另一个来表示路径。简单来看,也就是贪心的思路,由s点出发,首先是s->y=5,所以s->y的最短路劲已确定。后面比较权值大小,从y开始找边,可确定s->z的最短路径为7。后面再比较权值大小,从t开始找边,确定s->x最短路径为8.后面以此类推。

代码:

void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{
	size_t srci = get_index(src);
	size_t n = _vertexs.size();
	dist.resize(n, MAX_W);
	pPath.resize(n, -1);

	dist[srci] = 0;
	pPath[srci] = srci;

	// 已经确定最短路径的顶点集合
	vector<bool> S(n, true);

	for (size_t j = 0; j < n; ++j)
	{
		int u = 0;
		W min = W_MAX;
		for (size_t i = 0; i < n; ++i) //选出s->目标点最小权值的那个,从它开始找边
		{
			if (S[i] == true && dist[i] < min)
			{
				u = i;
				min = dist[i];
			}
		}

		S[u] = false; //已确定s->u的最短路径
		for (size_t v = 0; v < n; ++v)// 松弛更新u连接顶点v  srci->u + u->v <  srci->v  更新
		{
			if (S[v] == true && _matrix[u][v] != W_MAX
				&& dist[u] + _matrix[u][v] < dist[v])
			{
				dist[v] = dist[u] + _matrix[u][v];
				pPath[v] = u;
			}
		}
	}
}

打印路径: pPath数组中的下标表示表示对应的顶点,存储的值表示上一个联通它的顶点下标。

void PrintShortPath(const V& src, const vector<W>& dist, const vector<int>& pPath)
	{
		size_t srci = get_index(src);
		size_t n = _vertexs.size();
		for (size_t i = 0; i < n; ++i)
		{
			if (i != srci)
			{
				// 找出i顶点的路径
				vector<int> path;
				size_t parenti = i;
				while (parenti != srci)
				{
					path.push_back(parenti);
					parenti = pPath[parenti];
				}
				path.push_back(srci);
				reverse(path.begin(), path.end());//需要逆置下

				for (auto index : path)
				{
					cout << _vertexs[index] << "->";
				}
				cout << dist[i] << endl;
			}
		}
	}

结果:

 补充:该算法不能用于权值为负数的情况,当从一个顶点(A)到另一个顶点(B) 确定最短路径时,若有其它边是负数,则A->B的路径不一定是最短路径。

2.1.2单源最短路径--Bellman-Ford算法

bellman—ford算法可以解决负权图的单源最短路径问题,思路是暴力去遍历所有的边,由于存在负权的边,所以遍历完一次所有的边后,还要再去遍历n-1次,去更新负权边的影响。

例如:

 当从s去遍历时,并不能确定s->t=6是最短路径,s->y->x->t比从s->t的权值还要小,原因就是存在带负权的边。当遍历完与s相连的边后,暂且确定了s->t,s->y的最短路径。再去遍历与t,y相连的边,确定t->x,t->y,t->z的最短路径,进而确定s->x,s->y,s->z的路径......。当遍历到有负权的边时,与该边相连点(假设为m)s->m的最短路劲可能变化。有n个顶点,所以要遍历n次。

代码:

	bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
	{
		size_t n = _vertexs.size();
	    size_t srci = get_index(src);

		dist.resize(n, W_MAX);
		pPath.resize(n, -1);

		dist[srci] =0;
		for (size_t k = 0; k < n; ++k)// 总体最多更新n轮
		{
			// i->j 更新松弛
			bool update = false;
			cout << "更新第:" << k << "轮" << endl;
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
				  	//dist[i]表示的是 srci -> i 的权值
					if (_matrix[i][j] != 0 && dist[i] + _matrix[i][j] < dist[j])
					{
						update = true;
						dist[j] = dist[i] + _matrix[i][j];
						pPath[j] = i;
					}
				}
			}

			// 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了
			if (update == false)
			{
				break;
			}
		}

		return true;
	}

如果存在带负权的回路呢?例如:

 只需判断一下即可

if (j != srci)
{
	if (_matrix[i][j] != 0 && dist[i] + _matrix[i][j] < dist[j])
	{
		update = true;
		cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
		dist[j] = dist[i] + _matrix[i][j];
		pPath[j] = i;
	}
}

 但下面这种情况:

在遍历n次后进行判断,若还能更新,即存在负权回路;

	for (size_t i = 0; i < n; ++i)
		{
			for (size_t j = 0; j < n; ++j)
			{
				// srci -> i + i ->j
				if(j!=srci)
                {
                   if (_matrix[i][j] != 0 && dist[i] + _matrix[i][j] < dist[j])
				  {
					cout << "带负权回路" << endl;
					return false;
				  }
                }
			}
		}
		return true;
	}

代码测试:

void test2()
{
	const char* str = "syztx";
	Grap<char, int,INT_MAX, true> g(str, strlen(str));
	g.AddEdge('s', 't', 10);
	g.AddEdge('s', 'y', 5);
	g.AddEdge('y', 't', 3);
	g.AddEdge('y', 'x', 9);
	g.AddEdge('y', 'z', 2);
	g.AddEdge('z', 's', 7);
	g.AddEdge('z', 'x', 6);
	g.AddEdge('t', 'y', 2);
	g.AddEdge('t', 'x', 1);
	g.AddEdge('x', 'z', 4);
	g.Print();
	vector<int> dist;
	vector<int> parentPath;
	g.BellmanFord('s', dist, parentPath);
	cout << "打印路径" << endl;
	g.PrintShortPath('s', dist, parentPath);
}

结果:

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

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

相关文章

虹科分享|论企业网络安全的重要性

拥有有效的企业网络安全不仅仅是让你的员工创建一个不是他们宠物名字的密码--除非他们的猫的名字至少有12个字符长&#xff0c;由大小写字母和符号组成。无论是经过充分研究的鱼叉式钓鱼尝试&#xff0c;还是绕过MFA&#xff0c;威胁者都变得更加大胆。随着全球各行业数据泄露事…

判断是否为平衡树

对二叉树有困惑的小伙伴可以看一下我之前的文章&#xff1a;二叉树&#xff08;一&#xff09;_染柒_GRQ的博客-CSDN博客二叉树&#xff08;二&#xff09;_染柒_GRQ的博客-CSDN博客二叉树&#xff08;三&#xff09;_染柒_GRQ的博客-CSDN博客点击上方链接即可查看。题目110. 平…

Ext2explore查看ext2/ext3/ext4 file

比如想查看Android system.img&#xff0c;file看起来是ext2文件&#xff0c;file system.img system.img: Linux rev 1.0 ext2 filesystem data, UUID49e89c77-3dc4-553f-a392-7d11ff348228 (extents) (large files) (huge files)2、windows下怎么看呢&#xff0c;Ext2explore…

Springboot——常用注解及实例

一、常用注解解释&#xff1a;ConfigurationBeanResourceSpringBootApplicationRestControllerRestController 注解包含了原来的 Controller 和 ResponseBody 注解&#xff0c;使用过 Spring 的朋友对 Controller 注解已经非常了解了&#xff0c;这里不再赘述&#xff0c; Resp…

【BSV应用范例】区块链上的自我主权身份

发表时间&#xff1a;2022年6月27日 信息来源&#xff1a;bsvblockchain.org 自我主权身份&#xff08;SSI&#xff09;只是一个空想吗&#xff1f; &#xff08;全球区块链组织联合创始人&#xff09;Jorge Sebastio对此表示&#xff1a;“并非如此&#xff01;” 更重要的是…

OS 学习笔记(7) 虚拟机

OS 学习笔记(7) 虚拟机 这篇笔记对应的王道OS 1.6 虚拟机&#xff0c;同时参考了 《Operating System Concepts, Ninth Edition》和 俗称ostep的《 Operating Systems: Three Easy Pieces》还有 《Operating Systems: Principles and Practice》 文章目录OS 学习笔记(7) 虚拟机…

软件测试之Android单元测试

根据维基百科的解释&#xff0c;单元测试又称为模块测试。是针对程序单元来进行正确性校验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中&#xff0c;一个单元就是单个程序&#xff0c;函数&#xff0c;过程等&#xff0c;对于面向对象编程&#xff0c;最小单元…

STL——string类

一、标准库中的string类 1.string类文档介绍 &#xff08;1&#xff09;字符串是表示字符序列的类。 &#xff08;2&#xff09;标准的字符串类提供了对此类对象的支持&#xff0c;其接口类似于标准字符容器的接口&#xff0c;但添加了专门用于操作单字节字符字符串的设计特…

IOS逆向--恢复Dyld的内存加载方式

之前我们一直在使用由dyld及其NSCreateObjectFileImageFromMemory/NSLinkModule API方法所提供的Mach-O捆绑包的内存加载方式。虽然这些方法我们今天仍然还在使用&#xff0c;但是这个工具较以往有一个很大的区别…现在很多模块都被持久化到了硬盘上。 roguesys 在 2022 年 2 …

还在用 OpenFeign?来试试 SpringBoot3 中的这个新玩意!

好久没发技术文章了&#xff0c;最近回到工作地&#xff0c;晚上有空又可以码码技术了&#xff0c;今天我们就来聊一个 Spring Boot3 中的新鲜玩意&#xff0c;声明式 HTTP 调用。 1. 由来 Spring Boot3 去年底就已经正式发布&#xff0c;我也尝了一把鲜&#xff0c;最近有空…

(02)Cartographer源码无死角解析-(53) 2D后端优化→位姿图优化理论(SPA)讲解、核型函数调用流程

讲解关于slam一系列文章汇总链接:史上最全slam从零开始&#xff0c;针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解&#xff1a;https://blog.csdn.net/weixin_43013761/article/details/127350885 文末…

Docker镜像部署至Rancher全局配置 以xxl-job-admin为例

流程以xxl-job-admin为例 1.基础环境 win/mac/linuxRancherDocker 2.下载源码 从Github上下载xxl-job xxl-jobGithub xxl-job官方地址 3.修改源码 打开 xxl-job 下的 xxl-job-admin 修改 application-properties 文件 修改数据库 修改为这种格式&#xff1a; 大括号包…

MPLS实验

目录实验要求mpls简介mpls工作过程实验的配置环回的配置R1和R5之间公网的ospf配置配置mpls-ldp配置R1和R5间的mplsvpn私网的rip及ospf的宣告配置公网mp-bgp的建立R2和R4上面的双向重发布R7和R8之间创建R7和R8间的mplsvpn配置静态路由及环回重发布实验要求 如图 要求&#xff1…

【C++修炼之路】15.C++继承

每一个不曾起舞的日子都是对生命的辜负 继承C继承一. 继承的概念及定义1.1 继承的引出1.2 继承的概念1.3 继承的定义二.基类和派生类对象赋值转换三.继承中的作用域3.1 作用域的概念3.2 举例说明同名冲突四.派生类的默认成员函数4.1 派生类的构造函数4.2 派生类的拷贝构造函数4…

【python学习笔记】:数据科学库操作(二)

接上一篇&#xff1a; 4、PIL Python Imaging Library(PIL) 已经成为 Python 事实上的图像处理标准库了&#xff0c;这是由于&#xff0c;PIL 功能非常强大&#xff0c;但API却非常简单易用。但是由于PIL仅支持到 Python 2.7&#xff0c;再加上年久失修&#xff0c;于是一群志…

如果写不好 SQL,有没有好用的报表软件?

业务和技术在做报表这件事情上&#xff0c;究竟有多大差别&#xff1f; 一家企业、一个组织&#xff0c;只要一直在经营和运作&#xff0c;因为税务和其他原因就需要通过数据报表来反映当期的经营管理状况。而“做报表”这个事情&#xff0c;在企业内部不管是业务人员还是技术人…

HTTP之Referrer和Referrer-policy

目录 HTTP之Referrer和Referrer-policy Referer Referrer-policy 如何设置referrer 盗链 防盗链的工作原理 绕过图片防盗链 利用https网站盗链http资源网站&#xff0c;refer不会发送 设置meta 设置referrerpolicy"no-referrer" 利用iframe伪造请求refe…

C语言指针变量的运算

指针变量保存的是地址&#xff0c;而地址本质上是一个整数&#xff0c;所以指针变量可以进行部分运算&#xff0c;例如加法、减法、比较等&#xff0c;请看下面的代码&#xff1a;#include<stdio.h>intmain(){ int a 10,*pa &a,*paa &a; double b 99.9,*pb &a…

JTAG和SWD调试器

文章目录一、调试器二、JTAG三、SWD三、各自优缺点一、调试器 当我们开发单片机程序时&#xff0c;通常是在Windows或Linux上进行代码编写和编译&#xff0c;但是单片机并不直接集成在电脑上&#xff0c;怎么验证我们的单片机程序是否正确并烧录到单片机中&#xff0c;此时就需…

某游戏平台检测加速辅助案例分析

加速类辅助会对游戏平衡造成极大的破坏&#xff0c;这类辅助会通过HOOK api的方式来达到修改游戏对时间判断的目的&#xff0c;一般情况下&#xff0c;在R3层&#xff0c;这类辅助会在 QueryPerformanceCounter TimeGetTime GettickCount这三个API上HOOK&#xff0c;修改他们的…