【ACM算法】-- 图论篇 - 最短路径

序言:
因为这节,就要开始求最短路径了,开始涉及到图的存储问题,我先说一下具体的算法思想:

求最短路径主要有两种算法:

  1. 求单源最短路径,即求从起点,到图上任意一点的距离最小,这个是迪杰斯特拉算法,存储用邻接表存储。
  2. 求任意两点间的最短路径,这个是弗洛伊德算法,存储用邻接矩阵来存储。

首先说一下弗洛伊德算法:

弗洛伊德算法目的是求任意两点间的最短路径。
在这里插入图片描述
在这里插入图片描述
总结下来就是:将图用邻接矩阵来存储,然后依次考察【1,N】这些节点,依次考察是否可以加入路径中构成当前的最短路径。例如:当前在考察节点ans[ i ] [ j ] ,考察加入节点1时的情况,这时我们就要算ans[ i ] [ 1 ] + ans[ 1 ] [ j ] 和上面的那个距离哪个短,然后选择短的来更新ans[ i ] [ j ] 。

普通的算法代码:这个算法代码是没有考虑优化的情况
在这里插入图片描述
从图中可以明显的看到,数据结构采用了三维数组,每一个当前加入节点的k的矩阵生成,都需要k-1状态下的矩阵,数据量庞大。但是考虑一个问题:

对于一个特定的元素ans[ i ] [ j ] 当我们向其中考察任意节点的时候,它的计算形式如图所示:
在这里插入图片描述
当我们考察红色的点,且经过K点时,红色的点是由黄色的点决定的,而对于单独考虑黄色的点,它在本次更新K节点的时候,它的值是由ans[i][k]+ans[k][k]与ans[i][k]所决定的,而ans[k][k]=0,故相当于未变,所以对于本次更新,它保持不变,进一步推广,即当我们考察任意一个节点M加入路径时,以M节点为横纵的两条线上的所有的矩阵值均不变,而恰巧,更新每一个矩阵中的值的时候,就是基于这两条线的所以这时候我们就可以把三维矩阵中的那个表示K的那一维去掉,使原来的矩阵降维成二维矩阵。
如:
在这里插入图片描述
此时代码极其简单。

第一题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路: 这就简单了,就是简单地弗洛伊德算法。

代码如下:

#include<stdio.h>
int ans[101][101];
int main(){
	int n,m;
	freopen("in.txt","r",stdin);
	while(scanf("%d%d",&n,&m)!=EOF){
		if(n==0&&m==0)break;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				ans[i][j]=-1;//矩阵初始化,用-1代表无穷 
			}
			ans[i][i]=0;//使得每一个节点到自己距离为0; 
		}
		while(m--){
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			ans[a][b]=ans[b][a]=c;//对于无向图邻接矩阵关于主对角线对称 
		} 
		for(int k=1;k<=n;k++){
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					if(ans[i][k]==-1||ans[k][j]==-1){
						continue;
					}
					if(ans[i][j]==-1||ans[i][j]>ans[i][k]+ans[k][j]){
						ans[i][j]=ans[i][k]+ans[k][j];
					}
				}
			}
		}//弗洛伊德算法,时间复杂度 O(n^3)
		printf("%d\n",ans[1][n]); 
	}
	return 0;
}

注: 弗洛伊德算法,当两节点之间有多余的边时,选择权值小的边写入邻接矩阵。

下面介绍一下迪杰斯特拉算法:

所谓迪杰斯特拉算法就是求单源对短路径的问题,如上题,因为是求1到n之间的最短路径,如果用弗洛伊德算法,未免有点浪费。故采用迪杰斯特拉算法: 迪杰斯特拉算法维持一个当前已知的最短路径的数组。并不断更新。
如下:Dis [ n ] 数组
在这里插入图片描述
每一个元素都存储源点到当前节点的路径的最小值。而每一次选择节点,就选择当前节点里面未被选过的节点的最小值,如上图,假设Dis[1]未被选过,则此次就选择节点1,如果节点1已经被选择了,则此时只能选择节点4,即Dis[4]作为当前选择的节点。选择节点后,更新Dis数组,如果从当前选择的节点到Dis数组中未被走过的节点的长度小于原来的数值,则更新。依次下去,知道图中所有点都已经被选择过为止。

如上题:用迪杰斯塔拉算法解法如下:

#include<stdio.h>
#include<vector>
using namespace std;
struct E{
	int next;
	int c;
};
vector<E> edge[101];//使用邻接表形式来存储图
//模板代码 
bool mark[101];
int Dis[101];
int main(){
	int n,m;
	freopen("in.txt","r",stdin);
	while(scanf("%d%d",&n,&m)!=EOF){
		if(n==0&&m==0)break;
		for(int i=1;i<=n;i++)edge[i].clear();
		while(m--){
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			E tmp;
			tmp.c=c;
			tmp.next=b;
			edge[a].push_back(tmp);
			tmp.next=a;
			edge[b].push_back(tmp);
		}
		//迪杰斯特拉模板算法开始 
		for(int i=1;i<=n;i++){
			Dis[i]=-1;
			mark[i]=false;
		}//初始化所有节点都没有被选中 
		Dis[1]=0;
		mark[1]=true;
		int newP=1;//选中源点 
		for(int i=1;i<n;i++){
		//因为每一个点都需要被选中,而此时源点已经被选中,所以只需要循环n-1次 
			for(int j=0;j<edge[newP].size();j++){
				int t=edge[newP][j].next;
				int c=edge[newP][j].c;
				if(mark[t]==true)continue;//如果当前节点已经被选中过了,则跳过 
				if(Dis[t]==-1||Dis[t]>Dis[newP]+c){
					Dis[t]=Dis[newP]+c;
				}//根据选中点来更新Dis数组 
			}
			int min=123123123;
			for(int j=1;j<=n;j++){
				if(mark[j]==true)continue;
				if(Dis[j]==-1)continue;
				if(Dis[j]<min){
					min=Dis[j];
					newP=j;
				}
			}
			//每一次更新后,均遍历一次Dis数组,选择可到达的,并且未被选中的节点作为下一个选中点 
			mark[newP]=true;
		}//模板算法结束 
		printf("%d\n",Dis[n]);
	}
	return 0;
}

总之:迪杰斯特拉的核心就是一个Dis数组,思想是:在更新未被选中的节点的值,选择未被选中的节点中的距离最小值。

第二题:
在这里插入图片描述
在这里插入图片描述
思路: 这道题与上一题的区别仅在于,多了一个开销问题,而这个也仅仅只是加一个条件而已。

代码如下:

#include<stdio.h>
#include<vector>
using namespace std;
struct Edge{
	int next;
	int cost;
	int length;
};
vector<Edge> edge[1001];
int Dis[1001];
bool mark[1001];
int Cost[1001];
int main(){
	int n,m;
	freopen("in.txt","r",stdin);
	while(scanf("%d%d",&n,&m)!=EOF){
		if(n==0&&m==0)break;
		for(int i=1;i<=n;i++){
			edge[i].clear();
			Dis[i]=-1;
			Cost[i]=0;
			mark[i]=false;
		}
		for(int i=0;i<m;i++){
			int a,b,d,p;
			scanf("%d%d%d%d",&a,&b,&d,&p);
			Edge tmp;
			tmp.next=b;
			tmp.length=d;
			tmp.cost=p;
			edge[a].push_back(tmp);
			tmp.next=a;
			edge[b].push_back(tmp);
		}
		int s,t;
		scanf("%d%d",&s,&t);
		Dis[s]=0;
		mark[s]=true;
		Cost[s]=0;
		int newP=s;
		for(int i=1;i<n;i++){
			for(int j=0;j<edge[newP].size();j++){
				int next=edge[newP][j].next;
				int length=edge[newP][j].length;
				int cost=edge[newP][j].cost;
				if(mark[next]==true)continue;
				if(Dis[next]==Dis[newP]+length&&Cost[next]>Cost[newP]+cost){
					Cost[next]=Cost[newP]+cost;
				}//当路径长度一样,则判断开销大小
				if(Dis[next]=-1||Dis[next]>Dis[newP]+length){
					Dis[next]=Dis[newP]+length;
					Cost[next]=Cost[newP]+cost;
				}
			}
			int min=123123123;
			for(int i=1;i<=n;i++){
				if(mark[i]==true)continue;
				if(Dis[i]==-1)continue;
				if(Dis[i]<min){
					min=Dis[i];
					newP=i;
				}
			}
			mark[newP]=true;
		}
		printf("%d %d",Dis[t],Cost[t]);
	}
	return 0;
}	

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ACM-ICPC(国际大学生程序设计竞赛)是一项面向大学生的计算机编程竞赛,涉及算法和数据结构等领域。在比赛中,选手需要解决一系列编程问题,使用合适的算法和数据结构来实现正确和高效的解决方案。 对于整理ACM-ICPC模板,以下是一些建议: 1. 了解比赛要求:首先,你需要了解ACM-ICPC比赛的具体要求和规则。这包括了解比赛所涉及的算法和数据结构,以及题目的类型和难度等。 2. 收集资料:收集与ACM-ICPC相关的资料,包括经典算法和数据结构的实现代码、常见问题的解题思路等。可以参考教材、博客、论文等资源。 3. 整理模板:将收集到的资料整理成模板。可以按照算法和数据结构的分类进行整理,例如排序算法、图算法、字符串算法等。对每个模板,添加必要的注释和示例代码,以便理解和使用。 4. 测试代码:对每个模板编写测试代码,确保它们的正确性和可靠性。可以使用已知的测试用例或自行设计测试用例。 5. 更新与扩充:定期更新和扩充模板,以适应ACM-ICPC比赛中新出现的算法和数据结构。同时,根据自己的经验和理解,对模板进行优化和改进。 6. 练习和复习:在比赛之前,利用整理好的模板进行练习和复习。尝试解决一些经典问题,使用模板中的算法和数据结构进行实现,并进行优化。 希望这些建议对你整理ACM-ICPC模板有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值