最短路问题(三种)

本文深入讲解了图论中的三种经典算法:Floyd-Warshall算法用于求解任意两点间的最短路径;SPFA算法用于求解带负权边情况下的最短路径;Dijkstra算法用于求解无负权边情况下的最短路径。文章通过实例和代码帮助读者理解算法原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:Floyd-Warshall算法

求多源最短路(即任意两点间的最短路), 啊哈磊的讲解十分易懂,http://bbs.ahalei.com/thread-4554-1-1.html

直接贴出代码

#include <stdio.h>
int main()
{
    int e[10][10],k,i,j,n,m,t1,t2,t3;
    int inf=99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值,只要比题目所给任意数大就行 
    //读入n和m,n表示顶点个数,m表示边的条数
    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);
        e[t1][t2]=t3;
    }

    //Floyd-Warshall算法核心语句
    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("%10d",e[i][j]);
        }
        printf("\n");
    }

    return 0;
}//求多源最短路 //邻接矩阵的方法,输出的是任意两点的距离矩阵 

 

二:spfa算法:是bellman算法的优化写法,用邻接链表和栈来写

求的是给定起点到任意点的带负权最短路

关于邻接链表,可见啊哈磊的讲解http://ahalei.blog.51cto.com/4767671/1391988

适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。 我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。

算法思想:我们用数组d记录每个结点的最短路径估计值,用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止

 

期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

 

实现方法:

  建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

判断有无负环:
  如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)

 


 

 

 

首先建立起始点a到其余各点的
最短路径表格

                                  

首先源点a入队,当队列非空时:
 1、队首元素(a)出队,对以a为起始点的所有边的终点依次进行松弛操作(此处有b,c,d三个点),此时路径表格状态为:

                                  

在松弛时三个点的最短路径估值变小了,而这些点队列中都没有出现,这些点
需要入队,此时,队列中新入队了三个结点b,c,d

队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e点),此时路径表格状态为:

                                 

在最短路径表中,e的最短路径估值也变小了,e在队列中不存在,因此e也要
入队,此时队列中的元素为c,d,e

队首元素c点出队,对以c为起始点的所有边的终点依次进行松弛操作(此处有e,f两个点),此时路径表格状态为:

                                 

在最短路径表中,e,f的最短路径估值变小了,e在队列中存在,f不存在。因此
e不用入队了,f要入队,此时队列中的元素为d,e,f

 队首元素d点出队,对以d为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:

 

 

                               

在最短路径表中,g的最短路径估值没有变小(松弛不成功),没有新结点入队,队列中元素为f,g

队首元素f点出队,对以f为起始点的所有边的终点依次进行松弛操作(此处有d,e,g三个点),此时路径表格状态为:


                               

在最短路径表中,e,g的最短路径估值又变小,队列中无e点,e入队,队列中存在g这个点,g不用入队,此时队列中元素为g,e

队首元素g点出队,对以g为起始点的所有边的终点依次进行松弛操作(此处只有b点),此时路径表格状态为:

                           

在最短路径表中,b的最短路径估值又变小,队列中无b点,b入队,此时队列中元素为e,b
队首元素e点出队,对以e为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:

 

                          

在最短路径表中,g的最短路径估值没变化(松弛不成功),此时队列中元素为b

队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e这个点),此时路径表格状态为:

                         

在最短路径表中,e的最短路径估值没变化(松弛不成功),此时队列为空了

最终a到g的最短路径为14

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std; 
#define MAXN 1000 
int first[MAXN];//first[i]记录以i为起点的最后一条边的储存位置 
int next[MAXN];//next[i]记录与i同起点的上一条边的储存位置 
int u[MAXN],v[MAXN],w[MAXN];
int dis[MAXN];//记录起点到i的最长的距离 
int book[MAXN];//标记点 
int c[MAXN];//统计次数 
int n,m;//n为顶点,m为边 
int flag;//标记 
int spfa(int s,int n){
	int i,j,k;
	queue<int > q;
	memset(dis,0x3f,sizeof(0x3f));
	dis[s]=0;
	memset(book,0,sizeof(book));
	memset(c,0,sizeof(c));
	q.push(s);
	book[s]=1;
	flag=0;
	while(!q.empty())
	{
		int x;
		x=q.front();
		q.pop();
		book[x]=0;//队头元素出队,并且消除标记
		for(k=first[k] ; k!=-1 ; k=next[k])//遍历顶点x的邻接表
		{
			int y=v[k];
            if(dis[x]+w[k]<dis[y])
            {
                dis[y]=dis[x]+w[k];  //松弛
                if(!book[y])  //顶点y不在队内
                {
                    book[y]=1;    //标记
                    c[y]++;      //统计次数
                    q.push(y);   //入队
                    if(c[y]>n)  //超过入队次数上限,说明有负环
                        return flag=0;
                }
            }
		}
	} 
}
int main()
{
	int x,y,z;
	while(~scanf("%d %d",&n , &m))
	{
		memset(first,-1,sizeof(first));
		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;
		}
		spfa(1,n);
        if(flag)
            printf("有负权回路\n");
        else
        {
            for(int i=1;i<=n;i++)
            	printf("%d ",dis[i]);
        }
	}
	return 0;
}
/* 
bellman算法: 
for(int k=1;k<=n-1;k++)//进行n-1次松弛 
	for(int i=1;i<=m;i++)//枚举每一条边 
		if(dis[v[i]]>dis[u[i]]+w[i])//尝试松弛每一条边 
			dis[v[i]]=dis[u[i]]+w[i]; 
*/ 

三:dijstra算法:

求给定起点到任意点的最短路(不能有负权)

http://ahalei.blog.51cto.com/4767671/1387799同出自啊哈磊

#include<stdio.h>
#include<string.h>
#define MAXN 1000 
#define INF 0x3f3f3f
int e[MAXN][MAXN];//存图 
int dis[MAXN];//记录起点到i的最长的距离 
int book[MAXN];//标记点 
int n,m;//n为顶点,m为边
void dijstra(){
	int i,j,k,u,v;
	int flag;
	memset(book,0,sizeof(book));
	for(i=1 ; i<=m ; i++){
		dis[i]=e[1][i];
	}
	book[1]=1;
	for(i=1 ; i<=n-1 ; i++)
	{
		flag=INF;
		for(j=1 ; j<=n ; j++)//找到每个过程中距1最近的点 
		{
			if(book[j]==0 && dis[j]<flag)
			{
				flag=dis[j];
				u=j;
			}
		}
		book[u]=1;
		for(v=1 ; v<=n ; v++)//进行松弛,判断是否经过转折点使之更小 
		{
			if(e[u][v]<INF)
			{
				if(dis[v]>dis[u]+e[u][v])
					dis[v]=dis[u]+e[u][v];
			}
		}
	}
	for(i=1 ; i<=n ; i++)
	{
		printf("1->%d %d\n",i,dis[i]);//求的是以1为起点的最短路
	}
}
int main()
{
	int x,y,z;
	while(~scanf("%d %d",&n,&m))
	{
		for(int i=1 ; i<=n ; i++)
			for(int j=1 ; j<=n ; j++)
			{
				if(i == j)	e[i][j]=0;
				else	e[i][j]=INF;
			}
		for(int i=1 ; i<=m ; i++){
			scanf("%d %d %d",&x,&y,&z);
			e[x][y]=z;//有向图 
		}
		dijstra();
	}
	return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值