最短路问题(short-path problem)是网络理论解决的典型问题之一,可用来解决管路铺设、线路安装、厂区布局和设备更新等实际问题。基本内容是:若网络中的每条边都有一个数值(长度、成本、时间等),则找出两节点(通常是源节点和阱节点)之间总权和最小的路径就是最短路问题。(摘自百度百科)
常见的最短路算法有floyd,dijkstra和spfa。
1.floyd
floyd可以说是最暴力的最短路求法了,但其实floyd是用的动态规划的思想,只不过floyd可以求出任意2点之间的最短路,所以时间复杂度O(n^3)。
1 #include2 #include 3 #include 4 using namespace std; 5 int a[110][110]; 6 int x,y,v; 7 int main() 8 { 9 int n,m;//n个点,m条边 10 memset(a,127/3,sizeof(a)); 11 for(int i=1;i<=m;++i) 12 { 13 scanf("%d%d%d",&x,&y,&v); 14 a[x][y]=a[y][x]=v;//双向边 15 } 16 for(int k=1;k<=n;++k) 17 for(int i=1;i<=n;++i) 18 for(int j=1;j<=n;++j) 19 a[i][j]=min(a[i][k]+a[k][j],a[i][j]);//松弛操作 20 printf("%d",a[1][n]); 21 return 0; 22 }
例题:https://www.luogu.org/problemnew/show/P2888
多组询问,n<=300,裸的floyd,但是这题稍微变一下形,因为不是求路径长度,而是求路上最高的跨栏的高度,只需要把floyd的加和变成取max就好了。
1 #include2 #include 3 #include 4 using namespace std; 5 int n,m,t; 6 int a[310][310]; 7 int tmp1,tmp2,tmp3; 8 int read() 9 { 10 int x=0,f=1; 11 char ch=getchar(); 12 while(!isdigit(ch)) 13 { 14 if(ch=='-')f=0;ch=getchar(); 15 } 16 while(isdigit(ch)) 17 { 18 x=(x<<1)+(x<<3)+ch-'0';ch=getchar(); 19 } 20 return f?x:-x; 21 22 } 23 int main() 24 { 25 memset(a,127/3,sizeof(a)); 26 n=read(),m=read(),t=read(); 27 for(int i=1;i<=m;++i) 28 { 29 tmp1=read(),tmp2=read(),tmp3=read(); 30 a[tmp1][tmp2]=tmp3; 31 } 32 for(int k=1;k<=n;++k) 33 for(int i=1;i<=n;++i) 34 for(int j=1;j<=n;++j) 35 if(max(a[i][k],a[k][j])max(a[i][k],a[k][j]); 36 for(int i=1;i<=t;++i) 37 { 38 tmp1=read(),tmp2=read(); 39 if(a[tmp1][tmp2]<214748364)cout<endl; 40 else cout<<-1<<endl; 41 } 42 return 0; 43 }
floyd还可以用来求图的连通性,判断2个点是否联通
1 for(int k=1;k<=n;++k) 2 for(int i=1;i<=n;++i) 3 for(int j=1;j<=n;++j) 4 f[i][j]|=(f[i][k]&f[j][k])//如果i j本身联通,就是联通了,如果i k联通,k j联通,那么i j也联通
2.dijkstra
dijkstra解决的是单源最短路问题,时间复杂度O(n^2),dijkstra是贪心的思想,不能解决存在负边权的图。
dijkstra算法流程:从起点开始,更新每一个可以被更新的点,然后找出来一个距起点最近的点,用它更新所有可以被更新的点,依次循环n次。在这里可以被更新的点指的是所有用当前点松弛可以得到与起点更近的距离的点。
算法证明(窝自己瞎yy的,不喜勿喷):
最开始的时候找到的都是和起点联通的点,那么这里面的距离起点最近的点一定不可能在被其他点更新了,也就是它和起点的最短路就是它和起点连接的这条边了。有一个显而易见的性质就是,一个点的距离起点的最短路上的点到起点都是最短路,因为不是这样的话就会存在长度更小的路径。所以此时用第二个点更新出来的距离起点最小的点的最短路径长度也出来了,在用它去更新别人,依次更新n次,终点的最短路也就找到了。
我是很少用dijkstra的,因为基本上可以用spfa过。没找到例题,也不知道代码对不对就先不放了。
3.spfa
spfa应该是大部分oier最熟悉的最短路算法了,spfa之所以叫spfa是因为发明算法的人称之为 special fast algorithm,也就是非常快的意思啦,一般说spfa的复杂度是O(km)的,切k的一般为2,但实际证明并没有那么快,spfa会被稠密图卡。
例题:https://www.luogu.org/problemnew/show/P3371#sub
1 #include2 #include 3 #include 4 using namespace std; 5 int n,m,s; 6 vector<int>tu[10010];//cun dian 7 vector<int>v[10010];//cun bian quan 8 int from,to,val; 9 int q[10010*4]; 10 int head=1,tail=1; 11 bool vis[10010]; 12 int dis[10010]; 13 void spfa(){ 14 q[head]=s; 15 vis[s]=1; 16 while(head<=tail){ 17 int k=q[head]; 18 vis[k]=0; 19 head++; 20 for(int i=0;i i) 21 { 22 if(dis[k]+v[k][i]<dis[tu[k][i]]) 23 { 24 dis[tu[k][i]]=dis[k]+v[k][i]; 25 if(!vis[tu[k][i]])vis[tu[k][i]]=1,q[++tail]=tu[k][i]; 26 } 27 } 28 } 29 } 30 int main(){ 31 cin>>n>>m>>s; 32 for(int i=1;i<=n;++i)dis[i]=2147483647; 33 dis[s]=0; 34 for(int i=1;i<=m;++i) 35 { 36 scanf("%d%d%d",&from,&to,&val); 37 tu[from].push_back(to); 38 v[from].push_back(val); 39 } 40 spfa(); 41 for(int i=1;i<=n;++i) 42 cout< " "; 43 cout<<endl; 44 return 0; 45 }
模板题。
spfa还有一个应用是判负环,如果存在负边权回路,那么这个回路上的点会一直被更新,此时只要记录每一个点入队的次数,如果超过了n次,那么是一定存在负边权回路的。当然也有dfs的写法,每次如果可以更新就顺着这个点dfs下去,如果dfs到了一个已经dfs过的点,那么这个点属于一个环,并且是负环。
例题:https://www.luogu.org/problemnew/show/P3385
这题貌似会卡bfs版的spfa,所以放上dfs的代码好了
1 #include2 #include 3 #include 4 using namespace std; 5 const int maxn=200005; 6 int t; 7 int n,m; 8 struct zhw{ 9 int to,last,val; 10 }tu[maxn<<1]; 11 int head[maxn],tot=0; 12 int x,y,v; 13 void add(int x,int y,int v) 14 { 15 tot++; 16 tu[tot].last=head[x],head[x]=tot,tu[tot].to=y,tu[tot].val=v; 17 } 18 bool flag; 19 bool vis[maxn]; 20 int dis[maxn]; 21 void dfs(int x) 22 { 23 vis[x]=1; 24 for(int i=head[x];i;i=tu[i].last) 25 if(dis[tu[i].to]>dis[x]+tu[i].val) 26 { 27 if(vis[tu[i].to]||flag) 28 { 29 flag=1;break; 30 } 31 dis[tu[i].to]=dis[x]+tu[i].val;dfs(tu[i].to); 32 } 33 vis[x]=0; 34 } 35 int main() 36 { 37 scanf("%d",&t); 38 while(t--) 39 { 40 scanf("%d%d",&n,&m); 41 memset(head,0,sizeof(head)),tot=0,flag=0; 42 for(int i=1;i<=m;++i) 43 { 44 scanf("%d%d%d",&x,&y,&v); 45 if(v>0)add(x,y,v),add(y,x,v); 46 else 47 add(x,y,v); 48 } 49 for(int i=1;i<=n;++i) 50 { 51 dfs(i); 52 if(flag)break; 53 } 54 if(flag)printf("YE5\n"); 55 else printf("N0\n"); 56 } 57 }