最短路径问题明白纸

本文深入解析Floyd-Warshall、Dijkstra与Bellman-Ford三种图算法,涵盖多源最短路径、单源最短路径及负权边处理,探讨算法原理、实现与优化。
written by: 东篱下の悠然
1. Floyd-Warshall(多源最短路径问题)

问题重述:加权有向图中允许所有节点作为中转,如何求出任意两点之间的最短路径?

算法基本思想:最开始只允许经过一号点作为中转,接下来只允许经过一号点和二号点进行中 转……允许经过所有点进行中转,求任意两点之间的最短路径。(动态规划思想的一种体现)

实现过程:将每个点作为当前两点之间的中转点,不断更新任意两点之间的最短路径。

#include <bits/stdc++.h>

int n, m, e[50][50];
int main(){
	
int inf = 9999999;//存储一个我们认为是正无穷的值,其实只要大于边数的平方(m * m)即可
	
//初始化地图 
	scanf("%d%d", &n, &m);//输入节点个数和路径条数 
	for(int i = 1; i <= n; i++)
	for(int j = 1; j <= m; j++){
		if(i == j) e[i][j] = 0;//规定节点自己到节点自己的权为0
		else e[i][j] = inf; 
	} 
	
//输入权值,就是已有的节点到节点的距离 
	int a, b, r;
	for(int i = 1; i <= m; i ++){
		scanf("%d%d%d", &a, &b, &r);
		e[a][b] = r;
	} 
	
//Floyd-Warshall核心部分,只有五行,真是伟大
	for(int i = 1; i <= n; i ++)//允许经过i点中转 
		for(int j = 1; j <= n; j++)
			for(int k = 1; k <= n; k++)
			
			if (e[j][k] > e[j][i] + e[i][k])
			e[j][k] = e[j][i] + e[i][k];//更新最短路径 
	
//输出最终更新结果
	for(int i = 1; i <= n; i ++){
		for(int j = 1; j <= n; j++){
			printf("%2d", e[i][j]);
		}
		printf("\n");
	} 
	return 0;		
}

输入节点个数m和路径条数n,接下来n行输入两个节点的编号和它们之间的权值,用空格隔开。最终会返回一个m行m列的表格,表示j号节点(表格的第j行)到k号节点(表格的第k列)交界处的值即为最短路径。
总结:这种方法实现起来非常容易,可以求任意两边的最短路径,时间复杂度为O(N3)(有点慢)。此算法可以求指定两点之间的最短路径或指定一个点到其他所有点的最短路径。它可以处理带有“负权边”(即路径上的值小于零)的图,但不可以处理带有“负权环”的图(可能没有最短路径)。

上面介绍的floyd-warshall算法中有一个明显的缺点就是时间复杂度太高。接下来介绍一种可以将时间复杂度降低一个维度的算法:

2. Dijkstra算法(单源最短路径问题)

问题重述:加权有向图中允许所有节点作为中转,如何求出指定一点到其他所有点的最短路径?

算法基本思想:从顶点a开始,首先找到离a最近(权值最小)的顶点,设为x,再从x出发找所有的出边边,设点x直接连接了y和z,再来研究x到y这条路是不是让a到y的路程变得更短。在这里设点a到其他顶点的距离全部存储在dis数组中,点a能直接到的点则设初始值为相应的权,若不能直接到的点设置为inf。如果a到x 加 x到y 小于 a直接到y,则更新dis[y]的值,这个过程的专业术语叫“松弛”。

实现过程:找到离源点最近的点,以该点进行扩展并不断松弛dis中的数值,最终dis中的数值就是源点到其他点的最短路径。

#include <bits/stdc++.h>
int n, m, e[50][50];
int main(){
	
	int inf = 9999999;//存储一个我们认为是正无穷的值
	
//初始化地图 
	scanf("%d%d", &n, &m);//输入节点个数和路径条数 
	for(int i = 1; i <= n; i++)
	for(int j = 1; j <= m; j++){
		if(i == j) e[i][j] = 0;//规定节点自己到节点自己的权为0
		else e[i][j] = inf; 
	} 
	
//输入权值,就是已有的节点到节点的距离 
	int a, b, r;
	for(int i = 1; i <= m; i ++){
		scanf("%d%d%d", &a, &b, &r);
		e[a][b] = r;
	} 
	
//初始化book数组,一号顶点到其余点的初始路程 
	int dis[50];
	for(int i = 1; i <= n; i++)
		dis[i] = e[1][i]; 
	
//book数组初始化
	int book[50];
	for(int i = 1; i <= n; i ++)
		book[i] = 0;
	book[1] = 1;
	
//Dijkstra核心代码
	for(int i = 1; i <= n; i ++){
		//找离一号最近的点 
		int u;
		int min = inf;
		for(int j = 1; j <= n; j++){
			if(book[j] == 0 && dis[j] < min){
			 min = dis[j];
			 u = j; 
			}
		}
		book[u] = 1;
		for(int k = 1; k <= n; k++){
			if(e[u][k] < inf){
				if(dis[k] > dis[u] + e[u][k])
					dis[k] = dis[u] + e[u][k];
			}
		}
	}
//输出最终更新结果
	for(int i = 1; i <= n; i ++)
		printf("%d ",dis[i]);
	return 0;		
}

Dijkstra算法时间复杂为O(N2) 并且可以用“堆”来优化使复杂度降到O(logN)
缺点:不能解决带有负权边的图:

3. Bellman-Ford算法:解决负权边

问题重述:解决单源最短路径有负权边的问题。

算法基本思想:因为最短路径上最多有n-1条边,所以在此算法中最多有n-1个阶段,对每一条边进行松弛操作。每进行一次松弛就会有一些顶点已经求得最短路,即这些顶点的dis初始的“估计值”变为最短的“确定值”,并在此后这些点的最短路保持不变,不再受后续的松弛操作影响。

#include <bits/stdc++.h>

int n, m, e[50][50];
int main(){
	
	int inf = 9999999;//存储一个我们认为是正无穷的值
	
//读入边 
	int u[10], v[10], w[10];//记录边的信息,例如从u[i]到v[i]这条边权值为w[i] 
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++)
		scanf("%d%d%d", &u[i], &v[i], &w[i]); 

//初始化dis数组,就是一号顶点到其余各个顶点的初始路程
	int dis[10];
	for(int i = 1; i <= n; i ++)
		dis[i] = inf;
	dis[1] = 0;
	
//核心语句
	for(int k = 1; k <= n - 1; k++)
		for(int i = 1; i <= m; i++){
			if(dis[v[i]] > dis[u[i]] + w[i])
			   dis[v[i]] = dis[u[i]] + w[i];
		} 
	
	for(int i = 1; i <= n; i++)
	printf("%d ",dis[i]);
	
	return 0;
}

此外,它还可以检测一个图是否含有负权回路,如果进行完n-1轮松弛后,仍然存在

if(dis[v[i]] > dis[u[i]] + w[i])
	dis[v[i]] = dis[u[i]] + w[i];

的情况,仍然可以继续松弛,则存在负权回路。因为如果一个图没有负权回路,则最短路径所包含的边最多n-1条,即n-1轮松弛后路径不再变化。

//核心语句
	for(int k = 1; k <= n - 1; k++)
		for(int i = 1; i <= m; i++){
			if(dis[v[i]] > dis[u[i]] + w[i])
			   dis[v[i]] = dis[u[i]] + w[i];
		} 
//检测负权回路
	for(int i = 1; i <= m; i++){
		if(dis[v[i]] > dis[u[i]] + w[i])
			printf("含有负权回路");

总结:该算法的时间复杂度为* O(NM)*。因为上述代码只考虑最坏情况,最多会执行n-1轮松弛。实际上可以进行优化,可以添加一个check变量标记数组dis是否发生变化,若没有发生变化则可以跳出循环:

#include <bits/stdc++.h>

int n, m, e[50][50];
int main(){
	
	int inf = 9999999;//存储一个我们认为是正无穷的值
	
//读入边 
	int u[10], v[10], w[10];//记录边的信息,例如从u[i]到v[i]这条边权值为w[i] 
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++)
		scanf("%d%d%d", &u[i], &v[i], &w[i]); 

//初始化dis数组,就是一号顶点到其余各个顶点的初始路程
	int dis[10];
	for(int i = 1; i <= n; i ++)
		dis[i] = inf;
	dis[1] = 0;
	
//核心语句
	for(int k = 1; k <= n - 1; k++){
		int check = 0;
		
		for(int i = 1; i <= m; i++){
			if(dis[v[i]] > dis[u[i]] + w[i]){
				dis[v[i]] = dis[u[i]] + w[i];
				check = 1;
			}	   
		} 
		if(check == 0) break;
	}
//检测负权回路		
	for(int i = 1; i <= m; i++){
		if(dis[v[i]] > dis[u[i]] + w[i])
			printf("含有负权回路");
			
		else {
			for(int i = 1; i <= n; i++)
			printf("%d ",dis[i]);
		}
	} 
	return 0;
} 

Bellman-Ford优化算法每次仅对发生变化了的相邻的边进行松弛操作,但若要知道当前哪些点的最短路径发生了变化,可以用一个队列来维护这些点:

4. Bellman-Ford队列优化

算法基本思想:每次选取队首顶点u,对u的所有出边进行松弛操作,若源点到该顶点的松弛操作成功就将顶点加入队尾(会有重复顶点入队)。对该顶点所有出边松弛完毕后就将它出队,取下一个队首…直到队空为止。(与广度优先搜索bfs非常相似)

说到有重复顶点入队,需要用一个标记数组book去重:

#include <bits/stdc++.h>

int n, m, e[50][50];
int book[10];//全局变量默认填充0,表示节点都不在队列中 
int main(){
	
	int inf = 9999999;//存储一个我们认为是正无穷的值
	int dis[10] = {0};
	
//读入边 
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++)
		dis[i] = inf;
	dis[1] = 0;
	
	int first[6], next[8];//first = n + 1; next = m + 1
	for(int i = 1; i <= n; i++)
		first[i] = -1;
		
//读入每一条边
	int u[8], v[8], w[8];//要比m大1 
	for(int i = 1; i <= m; i++){
		scanf("%d%d%d", &u[i], &v[i], &w[i]);
		//建立邻接表 
		next[i] = first[u[i]];
		first[u[i]] = i;
	} 
	 
//一号顶点入队
	int head = 1, tail = 1;
	int queue[100] = {0};
	queue[tail] = 1;
	tail ++;
	book[1] = 1;
	
	while(head < tail){//队不空 
	
		int k = first[queue[head]];//需要处理的队首 
		
		while(k != -1){//扫描当前顶点所有出边
		
			if(dis[v[k]] > dis[u[k]] + w[k]){//判断是否松弛成功
				dis[v[k]] = dis[u[k]] + w[k]; //更新顶点到v[k]的路程 
				
				if(book[v[k]] == 0){//不在队列中 
					queue[tail] = v[k];
					tail ++;
					book[v[k]] = 1; 
				} 
			}
			k = next[k];
		} 
		//出队
		book[queue[head]] = 0;
		head ++;
	}	

	for(int i = 1; i <= n; i++)
		printf("%d ",dis[i]);
		
	return 0;
}

也可以不用book数组,只是每次更新后就要将用该顶点遍历队列找重复值,时间复杂度是O(N) ,使用book数组则复杂度为O(1)

♥如有谬误还请指正~~蟹蟹♥
内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值