题目链接:
难度二:P4779 【模板】单源最短路径(标准版) (慎入)
题目简述: 给出一个有向图,请输出从某一点出发到所有点的最短路径长度。
输入格式: 第一行包含三个整数N、M、S,分别表示点的个数、有向边的个数、出发点的编号。
接下来M行每行包含三个整数Fi、Gi、Wi,分别表示第i条有向边的出发点、目标点和长度。
输出格式 :仅一行,包含N个用空格分隔的整数,其中第i个整数表示从点S出发到点i的最短路径长度(若S=i则最短路径长度为0,若从点S无法到达点i,则最短路径长度为2147483647)
样例数据
输入:
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
输出:
0 2 4 3
第一种:Floyd算法
思想:
Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)
从任意节点i到任意节点j的最短路径不外乎2种可能,一是直接从i到j;二是从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的最短路径的距离。
优点:
容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。并可以处理负边权问题
缺点:
时间复杂度比较高,不适合计算大量数据。时间复杂度O(n^3),空间复杂度O(n^2)。且不能处理负环回路问题
初始化&&输入:
Floyd常用邻接矩阵(二维数组)来存储;即F[i][j]存储从点 i 到点 j 的距离dis
值得一提的是:在初始化时,要将所有的点存为+∞,表示从点 i 到点 j 无法到达,再将F[i][j]赋值为 0(i==j)
所有的 Dis[i]=MAXN;Dis[S]=0;//详见下楼代码
代码实现:
1 for(L k=1; k<=N; ++k)//枚举中间点!!!最关键的点
2 for(L i=1; i<=N; ++i )//枚举起点
3 for(L j=1; j<=N; ++j)//枚举终点
4 if(F[i][j]+ANS[i]<ANS[j]) //若经过中间点的距离小于已知最短的距离!!!
5 ANS[j]=F[i][j]+ANS[i];//更新数据
完整代码:
1 //Floyd版
2 #include<algorithm>
3 #include<cstdio>
4 #include<cstdlib>
5 #include<cstring>
6 #include<iostream>
7 using namespace std;
8 typedef long long L;
9 const L MAXN=2147483647;
10 L F[10010][10010],ANS[10010];
11 inline L read()//快速读入,PS:可以直接用scanf,此次是笔者打习惯了
12 {
13 L w=0,x=0;
14 char ch=0;
15 while(!isdigit(ch))w|=ch=='-',ch=getchar();
16 while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
17 return w?-x:x;
18 }
19 int main(void)
20 {
21 L N=read(),M=read(),S=read();
22 for(L i=1; i<=N; ++i)//初始化
23 {
24 for(L j=1; j<=N; ++j) F[i][j]=(i==j?0:MAXN);
25 ANS[i]=MAXN;
26 }
27 for(L i=1; i<=M; ++i)//输入
28 {
29 L A=read(),B=read(),l=read();
30 F[A][B]=min(F[A][B],l);
31 }
32 ANS[S]=0,F[1][1]=0;
33 for(L k=1; k<=N; ++k)//Floyd算法实现
34 for(L i=1; i<=N; ++i )
35 for(L j=1; j<=N; ++j)
36 if(F[i][j]+ANS[i]<ANS[j])
37 ANS[j]=F[i][j]+ANS[i];
38 for(L i=1; i<=N; ++i) printf("%lld ",ANS[i]);//输出
39 return 0;
40 }
41
评价:
这种算法虽然时间复杂度较高,但胜在容易理解,并且代码简便,清晰。在其他算法不会时可以骗个30分。(⊙o⊙)嗯!!!
第二种:Dijkstra算法
基本思想:
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
算法步骤:
1.初始时, From 只包含源点,即 From={S} 。 U 包含除 S 外的其他顶点,即:U={其余顶点}。 f 为 From 内一点, u 为 U 内一点。若从 f 能到 u 则 MAP[i] [j]!=MAXN,若f不能到 u ,则 MAP[i][j] 权值为 MAXN 。
2.从 U 中选取一个距离 S 最小的顶点 f ,把 f ,加入 From 中(该选定的距离就是 S 到 f 的最短路径长度)。
3.以 f 为新考虑的中间点,修改 U 中各顶点的距离:若从起点 S 到顶点 u 的距离(经过顶点 f )比原来距离(不经过顶点 f )短,则修改顶点 u 的距离值,修改后的距离值的顶点 f 的距离加上边上的权。
4.重复步骤 2 和 3 直到所有顶点都包含在 From 中。
基础版:
1.邻接矩阵存储:
//Dijkstra版(未优化)
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long L;
const L MAXN=2147483647;
L MAP[10010][10010],Dis[10010];
bool from[10010];
inline L read() //快读
{
L w=0,x=0;
char ch=0;
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return w?-x:x;
}
int main(void)
{
L N=read(),M=read(),S=read();
for(L i=1; i<=N; ++i)//初始化
{
for(L j=1; j<=N; ++j) MAP[i][j]=(i==j?0:MAXN);
Dis[i]=(i==S)?0:MAXN;
}
for(L i=1; i<=M; ++i) //输入
{
L f=read(),to=read(),dis=read();
MAP[f][to]=min(dis,MAP[f][to]);
}
for(L i=1; i<N; ++i)//共有N个顶点,除终点外还有N-1 个顶点
{
L MINN=MAXN,f=0;
for(L j=1; j<=N; ++j) //查找从S到u的最短路
if( (!from[j] ) && (Dis[j]<MINN) )//(!from[j])因为必须是从U中取出的顶点
MINN=Dis[j],f=j;
from[f]=true;//记录这点已在From中
for(L j=1; j<=N; ++j)
Dis[j]=(Dis[f]+MAP[f][j]<Dis[j])?Dis[f]+MAP[f][j]:Dis[j];//更新数据
}
for(L i=1; i<=N; ++i) printf("%lld ",Dis[i]);//输出
return 0;
}
2.链式前向星存储:(链式前向星可以减少存储空间,减少遍历次数)链式前向星的使用
1 #include<algorithm>
2 #include<cstdio>
3 #include<cstdlib>
4 #include<cstring>
5 #include<iostream>
6 using namespace std;
7 typedef long long L;
8 const L MAXN=2147483647;
9 struct Edge//前向星
10 {
11 L next;//下一条边的编号
12 L to;//这条边到达的点
13 L dis;//这条边的长度
14 } E[500010];
15 L num_edge=0,Head[10010],Dis[10010];
16 bool from[10010];
17 inline L read() //快读
18 {
19 L w=0,x=0;
20 char ch=0;
21 while(!isdigit(ch))w|=ch=='-',ch=getchar();
22 while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
23 return w?-x:x;
24 }
25 inline void Add_edge(L from,L to,L d)//前向星插入
26 {
27 ++num_edge;
28 E[num_edge].next=Head[from];
29 E[num_edge].to=to;
30 E[num_edge].dis=d;
31 Head[from]=num_edge;
32 }
33 int main(void)
34 {
35 L N=read(),M=read(),S=read();
36 for(int i=1; i<=N; ++i) Dis[i]=(i==S)?0:MAXN;//初始化
37 for(L i=1; i<=M; ++i)//输入
38 {
39 L u=read(),v=read(),d=read();
40 Add_edge(u,v,d);
41 }
42 L f=S;
43 while(!from[f])
44 {
45 from[f]=true;
46
47 for(L i=Head[f]; i!=0; i=E[i].next)//修改点距离
48 if( !from[E[i].to ] && (Dis[f]+E[i].dis<Dis[E[i].to]) )
49 Dis[E[i].to]=Dis[f]+E[i].dis;//更新
50
51 L MINN=MAXN;
52 for(L i=1; i<=N; ++i)//寻找最小点
53 {
54
55 if(!from[i]&&Dis[i]<MINN)
56 {
57 MINN=Dis[i];
58 f=i;
59 }
60 }
61 }
62 for(L i=1; i<=N; i++)printf("%lld ",Dis[i]);//输出
63 return 0;
64 }
优先队列(堆)优化版:
其中的 pair 参见: C++ std::pair的用法
priority_queue优先队列参见:STL--priority_queue用法
堆优化的原理就是,将寻找当前最小路径的结点这一操作,交由优先队列来执行
若是用大根堆,则在make_pair(-Dis[E[i].to],E[i].to);
用小根堆,则make_pair(Dis[E[i].to,E[i].to);//Dijkstra版(堆优化) #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<queue> #include<vector> using namespace std; typedef pair<int,int> node; const int MAXN=2147483647; struct Edge { int next;//下一条边的编号 int to;//这条边到达的点 int dis;//这条边的长度 } E[200010]; int num_edge=0,Head[100010],Dis[100010],N,M,S; bool Map[100010]; inline int read() //快读 { int w=0,x=0; char ch=0; while(!isdigit(ch))w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return w?-x:x; } inline void Add_edge(int from,int to,int d)//前向星存储试插入 { ++num_edge; E[num_edge].next=Head[from]; E[num_edge].to=to; E[num_edge].dis=d; Head[from]=num_edge; } inline void Scanf()//输入与初始化 { N=read(),M=read(),S=read(); for(int i=1; i<=N; ++i) Dis[i]=MAXN; Dis[S]=0; for(int i=1; i<=M; ++i) { int u=read(),v=read(),d=read(); Add_edge(u,v,d); } } inline void Printf()//输出答案 { for(int i=1; i<=N; i++)printf("%d ",Dis[i]); } inline void Dijkstra() { priority_queue< node,vector<node>,greater<node> > T; T.push(make_pair(0,S)); int head; while(!T.empty()) { head=T.top().second; T.pop(); if(Map[head])continue; else Map[head]=true; for(int i=Head[head]; i!=0; i=E[i].next) { if(Dis[head]+E[i].dis<Dis[E[i].to]) { Dis[E[i].to]=Dis[head]+E[i].dis; T.push(make_pair(Dis[E[i].to],E[i].to)); } } } } int main(void) { Scanf(); Dijkstra(); Printf(); return 0; }
第三种:Bellman-ford算法
基本思想:
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
算法步骤:
- 初始时, From 只包含源点,即 From={S} 。 U 包含除 S 外的其他顶点,即:U={其余顶点}。 f 为 From 内一点, u 为 U 内一点。若从 f 能到 u 则 MAP[i] [j]!=MAXN,若f不能到 u ,则 MAP[i][j] 权值为 MAXN 。
- 从 U 中选取一个距离 S 最小的顶点 f ,把 f ,加入 From 中(该选定的距离就是 S 到 f 的最短路径长度)。
- 以 f 为新考虑的中间点,修改 U 中各顶点的距离:若从起点 S 到顶点 u 的距离(经过顶点 f )比原来距离(不经过顶点 f )短,则修改顶点 u 的距离值,修改后的距离值的顶点 f 的距离加上边上的权。
- 重复步骤 2 和 3 直到所有顶点都包含在 From 中。
第四种:SPFA算法(队列优化的Bellman-ford)
1 #include<bits/stdc++.h> 2 const long long inf=2147483647; 3 const int maxn=10005; 4 const int maxm=500005; 5 using namespace std; 6 int n,m,s,num_edge=0; 7 int dis[maxn],vis[maxn],head[maxm]; 8 struct Edge 9 { 10 int next,to,dis; 11 }edge[maxm]; //结构体表示静态邻接表 12 void addedge(int from,int to,int dis) //邻接表建图 13 { //以下是数据结构书上的标准代码,不懂翻书看解释 14 edge[++num_edge].next=head[from]; //链式存储下一条出边 15 edge[num_edge].to=to; //当前节点编号 16 edge[num_edge].dis=dis; //本条边的距离 17 head[from]=num_edge; //记录下一次的出边情况 18 } 19 void spfa() 20 { 21 queue<int> q; //spfa用队列,这里用了STL的标准队列 22 for(int i=1; i<=n; i++) 23 { 24 dis[i]=inf; //带权图初始化 25 vis[i]=0; //记录点i是否在队列中,同dijkstra算法中的visited数组 26 } 27 q.push(s); dis[s]=0; vis[s]=1; //第一个顶点入队,进行标记 28 while(!q.empty()) 29 { 30 int u=q.front(); //取出队首 31 q.pop(); vis[u]=0; //出队标记 32 for(int i=head[u]; i; i=edge[i].next) //邻接表遍历,不多解释了(也可用vector代替) 33 { 34 int v=edge[i].to; 35 if(dis[v]>dis[u]+edge[i].dis) //如果有最短路就更改 36 { 37 dis[v]=dis[u]+edge[i].dis; 38 if(vis[v]==0) //未入队则入队 39 { 40 vis[v]=1; //标记入队 41 q.push(v); 42 } 43 } 44 } 45 } 46 } 47 int main() 48 { 49 cin>>n>>m>>s; 50 for(int i=1; i<=m; i++) 51 { 52 int f,g,w; 53 cin>>f>>g>>w; 54 addedge(f,g,w); //建图,有向图连一次边就可以了 55 } 56 spfa(); //开始跑spfa 57 for(int i=1; i<=n; i++) 58 if(s==i) cout<<0<<" "; //如果是回到自己,直接输出0 59 else cout<<dis[i]<<" "; //否则打印最短距离 60 return 0; 61 } //结束