• 最短路基础与习题


    目录

     

    前言

    Floyd算法

    Dijkstra算法

    详解

    模板

    Spfa算法

    详解

    模板

    例题

    A.POJ-2387 Til the Cows Come Home

    B.POJ-2253 Frogger

    C.POJ-1797 Heavy Transportation

    D:POJ-3268 Silver Cow Party

    E:POJ-1860 Currency Exchange

    F:POJ-3259 Wormholes

    G.POJ-1502 MPI Maelstrom

    H.POJ-3660 Cow Contest 


    前言

    最短路算法主要分为Floyd,Dijkstra,Spfa(由Bellman-Ford 算法优化而来),而且这三种算法都是非常重要的。最短路求值问题主要分为单源最短路与多源最短路。单源最短路即在图中求出给定顶点到其它任一顶点的最短路径,而多源最短路要在途中求出任意两个顶点的最短路径。

    Floyd算法

    是用来求任意两个结点之间的最短路的。复杂度比较高,但是常数小,容易实现。(我能说只有三个 for 吗?)

    适用于任何图,不管有向无向,边权正负,但是最短路必须存在。(不能有个负环)

    我们定义一个数组 f[k][x][y] ,表示只允许经过结点 1到 k,结点 x到结点 y的最短路长度。

    很显然, f[n][x][y] 就是结点x到结点 y的最短路长度。

    由此可以得到:f[k][x][y] = min(f[k-1][x][y], f[k-1][x][k]+f[k-1][k][y])。这个做法空间是  0(N^{3})

    但我们发现数组的第一维是没有用的,于是可以直接改成 f[x][y] = min(f[x][y], f[x][k]+f[k][y]) .

    for (k = 1; k <= n; k++) {
      for (i = 1; i <= n; i++) {
        for (j = 1; j <= n; j++) {
          f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
        }
      }
    }

    Dijkstra算法

    Dijkstra 算法,又叫迪科斯彻算法(Dijkstra),算法解决的是有向图中单个源点到其他顶点的最短路径问题。举例来说,如果图中的顶点表示城市,而边上的权重表示著城市间开车行经的距离,Dijkstra 算法可以用来找到两个城市之间的最短路径。附上大佬的详解Dijkstra算法图文详解与我的一个模板。

    详解

    指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”。例如求下图中的1号顶点到2、3、4、5、6号顶点的最

    短路径。

    下面我们来模拟一下:

     这就是Dijkstra算法的基本思路。

    模板

    void dijkstra(int u){   //u结点到n节点的最短路
    	int i,j,min1,v;
    	int dis[MAXV];
    	bool vis[MAXV];
    	for(i=1;i<=n;i++){
    		vis[i]=0;
    		dis[i]=map1[u][i];
    	}
            vis[u]=1;
    	for(i=1;i<=n;i++){
    		min1=inf;
    		for(j=1;j<=n;j++)
    			if(!vis[j] && dis[j]<min1){
    				v=j;
    				min1=d[j];
    			}
    		vis[v]=1;
    		for(j=1;j<=n;j++)
    			if(!vis[j] && dis[j]>map1[v][j]+dis[v])
    				dis[j]=map1[v][j]+dis[v];
    	}
    	printf("%d
    ",dis[n]);
    }
    

    Spfa算法

    SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法。

    很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。

    但是,SPFA算法稳定性较差,在稠密图中SPFA算法时间复杂度会退化。另附另一个大佬的Spfa算法图文详解与模板(谁让我不会写呢),这里只介绍下过程,证明可以参考下大佬的博客。

    详解

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

    模板

    void SPFA()
    {
        queue<int>q;
        for(int i=2;i<=n;i++){
            dis[i]=INF;
            vis[i]=0;
        }
        dis[1]=0;
        vis[1]=1;
        q.push(1);
        while(!q.empty()){
            int s=q.front();
            q.pop();
            vis[s]=0;
            for(int j=1;j<=n;j++){
                if(dis[j]>dis[s]+a[s][j]){
                    dis[j]=dis[s]+a[s][j];
                    if(!vis[j]){
                        q.push(j);
                        vis[j]=1;
                    }
                }
            }
        }
    
    }

    例题(大部分取自kuangbin带你飞

    A.POJ-2387 Til the Cows Come Home:简单的模板题,AC题解:

    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <ctype.h>
    #include <cstring>
    #include <cstdio>
    #include <sstream>
    #include <cstdlib>
    #include <iomanip>
    #include <string>
    #include <queue>
    #include <map>
    #define INF 0x3f3f3f3f
    #define PI 3.1415926
    #define MOD 1e9+7
    #define E 1e-6
    #define LL long long
    #define maxn 200007  //元素总个数
    #define ls l,m,rt<<1
    #define rs m+1,r,rt<<1|1
    
    using namespace std;
    #define inf 1<<29
    #define MAXV 1005
    
    int map1[MAXV][MAXV];
    int n,m;
    
    void dijkstra(){
    	int i,j,min1,v;
    	int d[MAXV];
    	bool vis[MAXV];
    
    	for(i=1;i<=n;i++){
    		vis[i]=0;
    		d[i]=map1[1][i];
    	}
            vis[1]=1;
    	for(i=1;i<=n;i++){
    		min1=inf;
    		for(j=1;j<=n;j++)
    			if(!vis[j] && d[j]<min1){
    				v=j;
    				min1=d[j];
    			}
    		vis[v]=1;
    
    		for(j=1;j<=n;j++)
    			if(!vis[j] && d[j]>map1[v][j]+d[v])
    				d[j]=map1[v][j]+d[v];
    	}
    	printf("%d
    ",d[n]);
    }
    
    int main(){
    	int i,j,a,b,c;
    	while(~scanf("%d%d",&m,&n)){
    		for(i=1;i<=n;i++)
    			for(j=1;j<=n;j++)
    				if(i==j)
    					map1[i][i]=0;
    				else map1[i][j]=map1[j][i]=inf;
    
    		for(i=1;i<=m;i++){
    			scanf("%d%d%d",&a,&b,&c);
    			if(map1[a][b]>c) map1[a][b]=map1[b][a]=c;
    		}
    		dijkstra();
    	}
    	return 0;
    }

    B.POJ-2253 Frogger:最短路的变形,问从一个点到另一个点使经过的边中最长的边尽可能短,最短为多少。主要是理解if(dis[j]>max(dis[s],a[s][j]))  dis[j]=max(dis[s],a[s][j]);举个例子假如假设d[3]=3,d[2]=1,a[2][3]=2。那么根据题意显然dis[3]>max(dis[2],a[2][3])。根据题意理解即应该为dis[3]=max(dis[2],a[2][3]).AC题解:

    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <ctype.h>
    #include <cstring>
    #include <cstdio>
    #include <sstream>
    #include <cstdlib>
    #include <iomanip>
    #include <string>
    #include <queue>
    #include <map>
    #define INF 0x3f3f3f3f
    #define PI 3.1415926
    #define MOD 1e9+7
    #define E 1e-6
    #define LL long long
    #define maxn 200007  //元素总个数
    #define ls l,m,rt<<1
    #define rs m+1,r,rt<<1|1
    
    using namespace std;
    double a[205][205],dis[205];
    int vis[205];
    void SPFA(int n)
    {
        queue<int>q;
        for(int i=2;i<=n;i++){
            dis[i]=INF;
            vis[i]=0;
        }
        dis[1]=0;
        vis[1]=1;
        q.push(1);
        while(!q.empty()){
            int s=q.front();
            q.pop();
            vis[s]=0;
            for(int j=1;j<=n;j++){
                if(dis[j]>max(dis[s],a[s][j])){
                    dis[j]=max(dis[s],a[s][j]);
                    if(!vis[j]){
                        q.push(j);
                        vis[j]=1;
                    }
                }
            }
        }
    
    }
    int main()
    {
    	int x[205],y[205];
    	int k=0,n;
    	while(scanf("%d",&n)!=EOF){
            if(n==0)
                 break;
    		 k++;
            printf("Scenario #%d
    ",k);
            for(int i=1;i<=n;i++)
                scanf("%d%d",&x[i],&y[i]);
            for(int i=1;i<=n;i++)
            for(int j=1;j<=i;j++){
                a[i][j]=a[j][i]=sqrt(double(x[i]-x[j])*(x[i]-x[j])+double(y[i]-y[j])*(y[i]-y[j]));
            }
            SPFA(n);
            printf("Frog Distance = %.3lf
    
    ",dis[2]);
    	}
    	return 0;
    }

    C.POJ-1797 Heavy Transportation:与上一题类似的变形题,只不过松弛条件换了,刚开始一直把载重量当成汽车要超过这个重量了,迷了很久,AC代码:

    #include <iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<queue>
    #include<set>
    #include<cmath>
    using namespace std;
    #define MAXV 1010
    #define min(a,b) (a<b?a:b)
    
    int map[MAXV][MAXV],n,m;
    
    int spfa(){
    	queue <int>q;
    	int i,j,v;
    	int vis[MAXV],d[MAXV];
    	for(i=1;i<=n;i++){
    		vis[i]=0;
    		d[i]=0;
    	}
    	q.push(1);
    	vis[1]=1;
    	while(!q.empty()){
    		v=q.front();q.pop();
    		vis[v]=0;
    
    		for(i=1;i<=n;i++){
    			if(v==1 && map[v][i]){
    				d[i]=map[v][i];
    				if(!vis[i]){
    					vis[i]=1;
    					q.push(i);
    				}
    				continue;
    			}
    			if(d[i]<min(d[v],map[v][i])){
    				d[i]=min(d[v],map[v][i]);
    				if(!vis[i]){
    					vis[i]=1;
    					q.push(i);
    				}
    			}
    		}
    	}
    	return d[n];
    }
    
    int main(){
    	int t,i,j,sum,a,b,c;
    	scanf("%d",&sum);
    	for(t=1;t<=sum;t++){
    		scanf("%d%d",&n,&m);
    		for(i=0;i<=n;i++)
    			for(j=0;j<=n;j++)
    				map[i][j]=0;
    		for(i=1;i<=m;i++){
    			scanf("%d%d%d",&a,&b,&c);
    			map[a][b]=map[b][a]=c;
    		}
    		printf("Scenario #%d:
    ",t);
    		printf("%d
    
    ",spfa());
    	}
    	return 0;
    }

    D:POJ-3268 Silver Cow Party:这道题目首先是计算从任意一点到指定点的距离,然后利用矩阵的转置,将矩阵变换好之后,再次Dijkstra,求出距离,然后加和求最大的距离即可,AC代码:

    #include <iostream>
    using namespace std;
    #define MAXV 1010
    #define inf 1<<29
     
    int map[MAXV][MAXV],d[MAXV],dback[MAXV];
    bool vis[MAXV];
    int n,m,x;
     
    int dijkstra(){
    	
    	int i,j,v,mi;
    	for(i=1;i<=n;i++){
    		vis[i]=0;
    		d[i]=map[x][i];
    		dback[i]=map[i][x];
    	}
    	
    	for(i=1;i<=n;i++){
    		mi=inf;
    		for(j=1;j<=n;j++)
    			if(!vis[j] && d[j]<mi){
    				v=j;
    				mi=d[j];
    			}
    			vis[v]=1;
    			
    			for(j=1;j<=n;j++){
    				if(!vis[j] && map[v][j]+d[v]<d[j])
    					d[j]=map[v][j]+d[v];
    			}
    	}
    	
    	for(i=1;i<=n;i++) vis[i]=0;
    	
    	for(i=1;i<=n;i++){
    		mi=inf;
    		for(j=1;j<=n;j++)
    			if(!vis[j] && dback[j]<mi){
    				v=j;
    				mi=dback[j];
    			}
    			vis[v]=1;
    			
    			for(j=1;j<=n;j++){
    				if(!vis[j] && map[j][v]+dback[v]<dback[j])
    					dback[j]=map[j][v]+dback[v];
    			}
    	}
    	mi=-1;
    	for(i=1;i<=n;i++){
    		if(d[i]+dback[i]>mi)
    			mi=d[i]+dback[i];
    	}
    	return mi;
    }
     
    int main(){
    	int i,a,b,c,j;
    	while(~scanf("%d%d%d",&n,&m,&x)){
    		for(i=1;i<=n;i++){
    			for(j=1;j<=n;j++)
    				if(i!=j) map[i][j]=inf;
    				else map[i][j]=0;
    		}
    		
    		for(i=1;i<=m;i++){
    			scanf("%d%d%d",&a,&b,&c);
    			map[a][b]=c;
    		}
    		
    		printf("%d
    ",dijkstra());
    	}
    	return 0;
    }

    E:POJ-1860 Currency Exchange:判断是否存在正环回路,可以参考下我写的题解Currency Exchange题解

    F:POJ-3259 Wormholes:判断是否存在负环问题在 SPFA算法中,每次松弛的时候,会吧初始点的访问下表变为0,如果图里面存在环的话,SPFA算法是无法结束的,利用这个思维,在一个有n个点的图中,如果不存在自身环下某一个点顶多被所有其他的点相连,这样的话,这个点顶多进队列n-1次,如果存在环,这个点进队的次数一定会大于n-1,利用这个原理,进行一遍SPFA就可以得到答案了.AC代码:

    #include <iostream>
    #include <queue>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int inf=0x3f3f3f;
    const int N=1200;
    int vis[N];
    int dir[N];
    int num[N];
    int n,m,w;
    struct st
    {
        int id;
        int wi;
    } a;
    vector<st>v[N];
    int SPFA()
    {
        memset(vis,0,sizeof(vis));
        memset(dir,inf,sizeof(dir));
        memset(num,0,sizeof(num));
        vis[2]=0;
        dir[2]=0;
        queue<int>q;
        q.push(2);
        num[2]++;
        while(!q.empty())
        {
            int b=q.front();
            if(num[b]>=n)
            {
                return 1;
            }
            q.pop();
            vis[b]=0;
            for(int i=0; i<v[b].size(); i++)
            {
                int id=v[b][i].id;
                int wi=v[b][i].wi;
                if(dir[id]>dir[b]+wi)
                {
                    dir[id]=dir[b]+wi;
                    if(!vis[id])
                    {
                        vis[id]=1;
                        q.push(id);
                        num[id]++;
                    }
                }
            }
     
        }
        return 0;
    }
    int main ()
    {
        int t;
        int x,y,z;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d%d",&n,&m,&w);
            for(int i=0; i<=n; i++)
                v[i].clear();
            for(int i=0; i<m; i++)
            {
                scanf("%d%d%d",&x,&y,&z);
                a.id=y;
                a.wi=z;
                v[x].push_back(a);
                a.id=x;
                a.wi=z;
                v[y].push_back(a);
            }
            for(int i=0; i<w; i++)
            {
                scanf("%d%d%d",&x,&y,&z);
                a.id=y;
                a.wi=-z;
                v[x].push_back(a);
            }
            int flag=SPFA();
            if(flag)printf("YES
    ");
            else printf("NO
    ");
        }
    return 0;
    }

    G.POJ-1502 MPI Maelstrom:求1开始到其他点的最短路径中,最长的那个是多少。题解:

    #include <iostream>
    #include <string.h>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    using namespace std;
    #define MAXV 102
    #define INF 100000
    
    int map[MAXV][MAXV],n;
    
    void dijstra(){
    	int i,j,ans=-1,min,v;
    	int d[MAXV],vis[MAXV];
    	//d数组表示从原点到i点的最短距离
    	//vis用于表达这个点是否已经被选中
    	for(i=1;i<=n;i++){
    		d[i]=INF;
    		vis[i]=0;
    	}
    	d[1]=0;		//因为start到start的距离为0,这里源点为1
    
    	for(i=1;i<=n;i++){
    		min=INF;
    		for(j=1;j<=n;j++){		//每次找点的过程,首先这个点没有被发现,然后找一个最小点
    			if(!vis[j] && d[j]<min){
    				min=d[j];
    				v=j;
    			}
    		}
    		//这里为什么找的最小的边就一定是最短路呢
    		//因为一个图要连通起来,就必须有一条边和已知点集连起来,所以找的最小的未知点必是最短路
    		vis[v]=1;
    
    		for(j=1;j<=n;j++)		//加进最小点后,再修改从源点没有被发现的点的最短路径
    			if(!vis[j] && d[v]+map[v][j]<d[j])
    				d[j]=d[v]+map[v][j];
    	}
    
    	for(i=2;i<=n;i++)
    		if(d[i]>ans) ans=d[i];
    	printf("%d
    ",ans);
    }
    
    int main(){
    	char s[10];
    	int i,j;
    	while(~scanf("%d
    ",&n)){
    		for(i=1;i<=n;i++)
    			for(j=1;j<=n;j++)
    				if(i!=j)
    					map[i][j]=INF;
    				else
    					map[i][j]=0;
    		for(i=2;i<=n;i++)
    			for(j=1;j<i;j++){
    				scanf("%s",s);
    				if(s[0]!='x')
    					map[i][j]=map[j][i]=atoi(s);	//将字符串转换为数字
    			}
    		dijstra();
    	}
    
    	return 0;
    }

    H.POJ-3660 Cow Contest :最短路的变形题,题解:

    #include <iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    int n,m;
    int map[101][101];
    void floyd()
    {
    	for(int k=1;k<=n;k++)
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				if(map[i][k]&&map[k][j])
    				   map[i][j]=1;
        int cnt=0;
        for(int i = 1;i<=n;i++){
        	int ans=n-1;
        	for(int j = 1;j<=n;j++){
        		if(map[i][j]||map[j][i])
        		   ans--;
    		}
    		if(ans==0) cnt++;
    	}
    	printf("%d
    ",cnt);
    }
    int main(int argc, char** argv) {
    	while(scanf("%d %d",&n,&m)!=EOF)
    	{
    		memset(map,0,sizeof(map));
    		for(int i = 0;i<m;i++){
    			int a,b;
    			scanf("%d %d",&a,&b);
    			map[a][b]=1;
    		}
    		floyd();
    	}
    	return 0;
    }
  • 相关阅读:
    算法总结7—多维缩放
    算法总结3—神经网络
    算法总结9—优化
    算法总结8—非负矩阵因式分解
    R语言系列—区间估计
    算法总结2—决策树分类器
    算法总结5&6k最近邻与聚类
    统计,逻辑与智能
    算法总结4—支持向量机
    R语言系列—回归分析
  • 原文地址:https://www.cnblogs.com/shmilky/p/14089055.html
Copyright © 2020-2023  润新知