最短路问题 - dijkstra算法、Bellman_Ford算法、SPFA模板、Floyd算法

寻找最短路径指的是找出从图中某个结点出发到达另一个结点所经过的边的权重之和最小的那条路径。这里的最短路不仅仅指一般意义上的距离最短,还可以引申到时间,费用等最小。

算法中的最短路问题类型:
1:单源最短路:给定一个源结点,求出这个点到其他所有点的最短路径,有Dijkstra和Bellman-ford两种算法,Dijkstra只能求解所有边权都为正的情况,Bellman-ford可以求解边权为正或为负但没有负环的情况。
2:多源最短路:求出图中每个结点到其他所有结点的最短路,可用Floyd算法解,Floyd同样可求解有负边无负环的情况。

3:点对点最短路:给定起点和终点,求这两点间的最短路径。通常用BFS(广搜)和DFS(深搜)求解。

dijkstra算法

需要设的变量:

1:cost数组,cost[i][j]代表从i到j的距离(或费用等任意权值)。

2:d数组,d[i]代表源点到结点i的最短路,初始值为无限大。

3:used数组,used[i]表示结点i是否被使用,初始为0,表示没被使用

算法步骤

初始化cost数组为INF,然后读入边的值

初始化d数组为INF,设d[s]=0,s为原点

初始化used数组为0,表示都没用过

1:将源点s的d值设置为0,used[s]=1,遍历与s有边相连的所有结点i,并将他们的d值设置为u,i之间的距离cost[u][i]。
2:在used[i]=0的结点中找一个到源点的距离最小的点v,并把它的used值设为1。
3:遍历与v有边相连的所有结点i,如果源点到i的距离d[i]大于从源点到v的距离d[v]与i,v之间的距离cost[i][v]的加和,那么将d[i]设置为d[v]+cost[i][v](这个步骤称为边的松弛,等于从源点到i时多走了一步,变成先到v再到i,松弛操作即松弛点,经过的点数变多,缩小距离)。
4:重复步骤2+3  n-1次,直到最后一个结点被加入,结束。

图解:

《最短路问题 - dijkstra算法、Bellman_Ford算法、SPFA模板、Floyd算法》

例题:

 

在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗? 
 

Input输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。 
输入保证至少存在1条商店到赛场的路线。 
Output对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间Sample Input

2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0

Sample Output

3
2

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=105,INF=0x3f3f3f3f;
int d[N],w[N][N],vis[N],n,m;

void Dijkstra(int s){
    for(int i=0;i<=n;i++){//每个点到原点的最短距离
        d[i]=INF;
    }
    d[s]=0;
    memset(vis,0,sizeof(vis));

    for(int i=1;i<=n;i++){
        int v=-1;
        for(int j=1;j<=n;j++){//对于每一个点,找到最近的一个点
            if(!vis[j]&&(v==-1||d[j]<d[v]))v=j;
        }

        vis[v]=1;
        for(int k=1;k<=n;k++){
            d[k]=min(d[k],d[v]+w[v][k]);
        }
    }
}

int main(){
    int a,b,c;
    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++){
                w[i][j]=INF;
            }
        }
        while(m--){
            scanf("%d%d%d",&a,&b,&c);
            w[a][b]=w[b][a]=c;
        }

        Dijkstra(1);
        printf("%d\n",d[n]);
    }
}

dijkstra算法的优化:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef pair<int,int> P;
const int N=105,INF=0x3f3f3f3f;
int d[N],n,m;

struct edge{
    int to,cost;
};

vector<edge>G[N];

void Dijkstra(int s){
    for(int i=1;i<=n;i++){//每个点到原点的最短距离
        d[i]=INF;
    }
    d[s]=0;
    priority_queue<P,vector<P>,greater<P> >q;

    q.push(P(d[s],s));//按边的权值排序

    while(!q.empty()){
       P p=q.top();q.pop();

       int v=p.second;//顶点的编号

       if(d[v]<p.first)continue;

       for(int i=0;i<G[v].size();i++){
           edge e=G[v][i];//点v到i的情况(点i的值和v到i的路径)
           if(d[e.to]>d[v]+e.cost){
               d[e.to]=d[v]+e.cost;
               q.push(P(d[e.to],e.to));
           }
       }
    }
}
int main(){
    int a,b,c;
    while(scanf("%d%d",&n,&m)!=EOF){
        if(n==0&&m==0)break;
        memset(G,0,sizeof(G));
        while(m--){
            scanf("%d%d%d",&a,&b,&c);
            G[a].push_back({b,c});
            G[b].push_back({a,c});
        }

        Dijkstra(1);
        printf("%d\n",d[n]);
    }
}

 

贝尔曼-福特算法和狄克斯特拉算法类似,只不过每次随机寻找边进行更新。

 

检查是否存在负:如果在进行过n-1次松弛操作后还存在可以松弛的边,那么说明有负环。

(如果没有负环的话松弛操作完后,d[i]就表示点i到原点最小的长度,还能再松弛说明有一个边是负的)

时间复杂度:O(VE),用队列优化复杂度为O(kE),k为每个节点入队次数,也就是SPFA算法。

《最短路问题 - dijkstra算法、Bellman_Ford算法、SPFA模板、Floyd算法》

例题:

 

While exploring his many farms, Farmer John has discovered a number of amazing wormholes. A wormhole is very peculiar because it is a one-way path that delivers you to its destination at a time that is BEFORE you entered the wormhole! Each of FJ’s farms comprises N (1 ≤ N ≤ 500) fields conveniently numbered 1..NM (1 ≤ M≤ 2500) paths, and W (1 ≤ W ≤ 200) wormholes.

As FJ is an avid time-traveling fan, he wants to do the following: start at some field, travel through some paths and wormholes, and return to the starting field a time before his initial departure. Perhaps he will be able to meet himself :) .

To help FJ find out whether this is possible or not, he will supply you with complete maps to F (1 ≤ F ≤ 5) of his farms. No paths will take longer than 10,000 seconds to travel and no wormhole can bring FJ back in time by more than 10,000 seconds.

Input

Line 1: A single integer, FF farm descriptions follow. 
Line 1 of each farm: Three space-separated integers respectively: NM, and W 
Lines 2.. M+1 of each farm: Three space-separated numbers ( SET) that describe, respectively: a bidirectional path between S and E that requires T seconds to traverse. Two fields might be connected by more than one path. 
Lines M+2.. MW+1 of each farm: Three space-separated numbers ( SET) that describe, respectively: A one way path from S to E that also moves the traveler backT seconds.

Output

Lines 1.. F: For each farm, output “YES” if FJ can achieve his goal, otherwise output “NO” (do not include the quotes).

Sample Input

2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8

Sample Output

NO
YES

Hint

For farm 1, FJ cannot travel back in time. 
For farm 2, FJ could travel back in time by the cycle 1->2->3->1, arriving back at his starting location 1 second before he leaves. He could start from anywhere on the cycle to accomplish this.

Bellman_Ford

 

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
 
#define INF 0x3f3f3f3f
 
using namespace std;
struct proc{int from,to,cost;};
proc edge[6000];
int d[505],n,m,w,cnt;
 
void addEdge(int s,int e,int c){
    edge[cnt].from=s;
    edge[cnt].to=e;
    edge[cnt++].cost=c;
}
int Bellman(){
    int flag;
    memset(d,INF,sizeof(d));
    d[1]=0;
    for(int i=1;i<=n;i++){
        flag=0;
        for(int j=1;j<=cnt;j++){//存的边的数量
            if(d[edge[j].to]>d[edge[j].from]+edge[j].cost){
                d[edge[j].to]=d[edge[j].from]+edge[j].cost;
                flag=1;
             }
        }
        if(!flag)break;
    }
    //寻找负环
    
    for(int i=1;i<=cnt;i++){
        if(d[edge[i].to]>d[edge[i].from]+edge[i].cost)return 1;
    }
    return 0;
}
int main(){
    int f,s,e,c;
    scanf("%d",&f);
    while(f--){
        cnt=1;
        scanf("%d%d%d",&n,&m,&w);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&s,&e,&c);
            addEdge(s,e,c);
            addEdge(e,s,c);
        }
        for(int i=1;i<=w;i++){
            scanf("%d%d%d",&s,&e,&c);
            addEdge(s,e,-c);
        }
        if(Bellman())printf("YES\n");
        else printf("NO\n");
    }
}

SPFA算法:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring> 
#include<queue>

using namespace std;

const int INF=0x3f3f3f3f;
struct proc{int to,cost,next;};
proc edge[6000];
int d[505],vis[505],cnt[505],head[505];
int n,m,w,tot;

void init(){
	memset(vis,0,sizeof(vis));
	memset(head,-1,sizeof(head));
	memset(d,INF,sizeof(d));
	memset(cnt,0,sizeof(cnt));
	tot=0;
}

void addEdge(int from,int to,int cost){
	edge[tot].to=to;
	edge[tot].cost=cost;
	edge[tot].next=head[from];
	head[from]=tot;
	tot++;
}

int spfa(){
	queue<int>q;
	q.push(1);
	d[1]=0;
	vis[1]=1;
	cnt[1]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int p=head[u];p!=-1;p=edge[p].next){
			int v=edge[p].to;
			if(d[v]>d[u]+edge[p].cost){
				d[v]=d[u]+edge[p].cost;
				if(!vis[v]){
					vis[v]=1;
					cnt[v]++;
					if(cnt[v]>n)return 1;
					q.push(v);
				}
			}
		}
	}
	return 0;
}

int main(){
	int T,s,e,c;
	scanf("%d",&T);
	while(T--){
		init();
		scanf("%d%d%d",&n,&m,&w);
		for(int i=1;i<=m;i++){
			scanf("%d%d%d",&s,&e,&c);
		    addEdge(s,e,c);
		    addEdge(e,s,c);
		}
		for(int i=1;i<=w;i++){
			scanf("%d%d%d",&s,&e,&c);
			addEdge(s,e,-c);
		}
		if(spfa())printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

Floyd算法:

求解任意两点的最短路问题,设数组d[i][j]表示点i到j的最短路径,初始值:d[i][j]=INF(没有路径,i!=j),d[i][i]=0;

核心代码:

void floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}

例题:Floyd算法解决传递闭包问题

Cow Contest

N (1 ≤ N ≤ 100) cows, conveniently numbered 1..N, are participating in a programming contest. As we all know, some cows code better than others. Each cow has a certain constant skill rating that is unique among the competitors.

The contest is conducted in several head-to-head rounds, each between two cows. If cow A has a greater skill level than cow B (1 ≤ A ≤ N; 1 ≤ B ≤ NA ≠ B), then cow A will always beat cow B.

Farmer John is trying to rank the cows by skill level. Given a list the results of M(1 ≤ M ≤ 4,500) two-cow rounds, determine the number of cows whose ranks can be precisely determined from the results. It is guaranteed that the results of the rounds will not be contradictory.

Input

* Line 1: Two space-separated integers: N and M
* Lines 2..M+1: Each line contains two space-separated integers that describe the competitors and results (the first integer, A, is the winner) of a single round of competition: A and B

Output

* Line 1: A single integer representing the number of cows whose ranks can be determined
 

Sample Input

5 5
4 3
4 2
3 2
1 2
2 5

Sample Output

2

题意:n个人m个回合,在m行中,给出a,b表示a能打败b,问经过m个回合,n个人中,几个人的等级可以确定

传递闭包即是:已知i能到j,j能到k,则i能到k

若1个人与其他n-1个人的关系可以确定,那么他的等级就可以确定。

要注意的是这里的d数组初始化为0,表示还不知道i,j的关系,有关系则为1,这里设为INF就不行啦吧啊哈哈。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
int n,m;
int d[103][103];

void floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j]=d[i][j]||(d[i][k]&&d[k][j]);
            }
        }
    }
}

int main(){
    int a,b;
    scanf("%d%d",&n,&m);
    memset(d,0,sizeof(d));
    while(m--){
        scanf("%d%d",&a,&b);
        d[a][b]=1;
    }
    floyd();
    int ans=0;
    for(int i=1;i<=n;i++){
        int sum=0;
        for(int j=1;j<=n;j++){
            if(d[i][j]||d[j][i])sum++;
        }

        if(sum==n-1)ans++;
    }
    printf("%d\n",ans);
}

 

 

 

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