最短路径算法 模板_Dijkstra_Bellman.ford_Floyd_spfa

1.图的邻接表 模板

邻接表详讲

#include <stdio.h>
int main()
{
	int u[10],v[10],w[10],first[10],next[10];
	int n,m,i,j;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		first[i] = -1;
	}
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u[i],&v[i],&w[i]);
		next[i] = first[u[i]];        // first[u[i]] 保存定点u[i] 的第一条边的编号 
		first[u[i]] = i;          //next[i] 存储编号为i的边的下一条边的 编号 
	} 
	printf("\n\n\n");
	int k;
	for(i=1;i<=n;i++)
	{
		k = first[i];
		while(k!=-1)
		{
			printf("%d %d %d\n",u[k],v[k],w[k]);
			k = next[k];
		}
	}
	return 0;
} 

2 Dijkstra 算法

    时间复杂度为O((M+N)log N),空间复杂度为O(m),
    适合稠密图,不可以处理负权问题

       其基本原理是:每次新扩展一个距离最短的点,更新与其相邻的点的距离。当所有边权都为正时,由于不会存在一个距离更短的没扩展过的点,所以这个点的距离永远不会再被改变,因而保证了算法的正确性。不过根据这个原理,用Dijkstra求最短路的图不能有负权边,因为扩展到负权边的时候会产生更短的距离,有可能就破坏了已经更新的点距离不会改变的性质。

算法步骤:

a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。

b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。

c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。

d.重复步骤b和c直到所有顶点都包含在S中。

#include <stdio.h>
#include <string.h>
int main()
{
	int e[10][10],dist[10],book[10],i,j,n,m,t1,t2,t3,u,v,min;
	int inf = 0x3f3f3f3f;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
		{
			if(i==j)
			{
				e[i][j] = 0;
			}
			else
			{
				e[i][j] = inf;
			}
		}
	}
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&t1,&t2,&t3);
		if(e[t1][t2]>t3)
		{
			e[t1][t2] = e[t2][t1] = t3;
		}
	}
	memset(dist,inf,sizeof(dist));
	for(i=1;i<=n;i++)
	{
		dist[i] = e[1][i];
	}
	for(i=1;i<=n;i++)
	{
		book[i] = 0;
	}
	book[1] = 1;
	for(i=1;i<=n-1;i++)
	{
		min = inf;
		for(j=1;j<=n;j++)
		{
			if(!book[j] && dist[j]<min)
			{
				min = dist[j];
				u = j;
			}
		}
		book[u] = 1;
		for(v=1;v<=n;v++)
		{
			if(e[u][v]!=inf && dist[v] > dist[u] + e[u][v])
			{
				dist[v] = dist[u] + e[u][v];
			}
		}
	}
	for(i=1;i<=n;i++)
	{
		printf("%d ",dist[i]);
	}
	return 0;
}
/*
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4

0 1 8 4 13 17
*/

3.Bellman_ford 算法

/*
Bellman_ford 算法时间复杂度 O(N*M) ,空间复杂度O(m),适合稀疏图,可以解决负权问题。
*/

算法描述
输入:图 和 源顶点src
输出:从src到所有顶点的最短距离。如果有负权回路(不是负权值的边),则不计算该最短距离,
没有意义,因为可以穿越负权回路任意次,则最终为负无穷。

算法步骤:

1.初始化:将除源点外的所有顶点的最短距离估计值 d[v] ← +∞, d[s] ←0;
2.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)
3.检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。

#include <stdio.h>
int main()
{
	int dist[10],bak[10],i,k,n,m,u[10],v[10],w[10],check,flag;
	int inf = 0x3f3f3f3f;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u[i],&v[i],&w[i]);
	}
	for(i=1;i<=n;i++)
	{
		dist[i] = inf;
	}
	dist[1] = 0;
	for(k=1;k<=n-1;k++)
	{
		for(i=1;i<=n;i++)
		{
			bak[i] = dist[i];//把记录距离的数组备份一下 
		}
		for(i=1;i<=m;i++)
		{
			if(dist[v[i]]>dist[u[i]]+w[i])
			{
				dist[v[i]]=dist[u[i]]+w[i];	
			}
		}
		check = 0; //松弛后检测dist数组是否有更新 
		for(i=1;i<=n;i++)
		{
			if(bak[i]!=dist[i])
			{
				check = 1;
				break;
			} 
		} 
		if(check==0) //如果没有更新,提前退出循环结束算法 
		{
			break;
		}
	}
	flag = 0;        //判断是否有负权回路 
	for(i=1;i<=m;i++)
	{
		if(dist[v[i]]>dist[u[i]]+w[i])
		{
			flag = 1;
		}
		if(flag)
		{
			printf("此图含有负权回路\n");
		}
	}
	for(i=1;i<=n;i++)
	{
		printf("%d ",dist[i]);
	}
	printf("\n"); 
	return 0;
}

/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3

0 -3 -1 2 4
*/

4 Floyed算法

/*
Floyed算法,空间复杂度为o(n^2),时间复杂度为o(n^3),适合稠密图,可以解决带负权问题
*/

1)算法思想原理:

     Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)

      从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

2).算法描述:

a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。   

b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

#include <stdio.h>
int main()
{
	int e[10][10],k,i,j,n,m,t1,t2,t3;
	int inf = 0x3f3f3f3f;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			if(i==j)
			{
				e[i][j] = 0;
			}
			else
			{
				e[i][j] = inf;
			}
		}
	}
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&t1,&t2,&t3);
		e[t1][t2] = t3;
	}
	for(k=1;k<=n;k++)
	{
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=n;j++)
			{
				if(e[i][j]>e[i][k]+e[k][j])
				{
					e[i][j] = e[i][k] + e[k][j];
				} 
			} 
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
		{
			printf("%8d ",e[i][j]);
		}
		printf("\n");
	}	
	return 0;
}
/*
4 8
1 2 2
1 3 6
1 4 4
2 3 3 
3 1 7
3 4 1
4 1 5
4 3 12

*/ 

5 spfa算法

/*
spfa算法 空间复杂度O(m),时间复杂度最坏也是O(m*n),适合稀疏图,可以解决负权
判断负权,如果某个定点入队次数超过n次,那个这个图肯定存在负权。

算法大致流程。初始化将源点加入队列,每次从队首(head)中取出一个顶点,并对与
其相邻的所有定点进行松弛尝试,若某个相邻的顶点松弛成功,且这个相邻的顶点不在队列中
(不在head和tail之间),则将它加入到队列中。对当前顶点处理完毕后立即出队,并对下一个
新队首进行如下操作,直到队列为空时算法结束。

该算法与广度优先遍历算法十分相似,不同的是广度优先遍历的时候一个顶点出队后通常就不会再重新
进入队列, 而这里一个顶点出队后,可能会再重新进入队列。
*/

#include <stdio.h>
int main()
{
	int n,m,i,j,k;
	int u[8],v[8],w[8];//要比m的最大值大一 
	int first[6],next[8];//first数组要比n的最大值大一,next数组要比m的最大值大一 
	int dist[6]={0};
	int book[6]={0};//book数组记录哪些定点已经在队列中 
	int que[101]={0},head=1,tail=1;
	int inf = 0x3f3f3f3f;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		dist[i] = inf; 
		book[i] = 0;
		first[i] = -1; 
	}
	dist[1] = 0;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u[i],&v[i],&w[i]);
		next[i] = first[u[i]];
		first[u[i]] = i;
	}
	que[tail] = 1; //1号顶点入队 
	tail ++ ;
	book[1] = 1;
	while(head<tail)
	{
		k = first[que[head]];
		while(k!=-1)
		{
			if(dist[v[k]]>dist[u[k]]+w[k])
			{
				dist[v[k]] = dist[u[k]] + w[k];
				if(book[v[k]]==0)     //判断v[k]是否在队列中,不在,加入队列中 
				{
					que[tail] = v[k];
					tail ++ ;
					book[v[k]] = 1;
				}
			}
			k = next[k];
		}
		book[que[head]] = 0;
		head++;
	}
	for(i=1;i<=n;i++)
	{
		printf("%d ",dist[i]);
	}
	printf("\n");
	return 0;
} 
/*
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6

0 2 5 9 9
*/

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/huangshuai147/article/details/71155221
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞