图论算法->最短路
求最短路算法,有Floyd,dijkstra,Bellmanford,spfa等诸多高级算法。优化方法也是层出不穷。
我们不妨分析一下各算法的使用特点(可能不准确
1.Floyd算法 复杂度O(n³)可计算任意两点间最短路径 可处理负边权情况
2.Dijkstra算法 复杂度O(n²)只能计算单源最短路径 不可处理负边权情况(多源可再加一重循环)
3.Bellman-Ford算法 复杂度O(边数*点数) 计算单源最短路径 能处理负边权情况但无法处理存在负权回路情况
4.spfa算法 复杂度O(边数*玄学常数) 单源最短路径 可处理负边权情况
另外,这种算法在稀疏图上是好,而在稠密图有不乐观的一面。
另外,还有输出最短路径方案,记录前驱。参见玛丽卡一题。
我们不妨先来讨论两种图的存储方式
第一种是邻接矩阵,开了一个二维数组。如f[i][j]记录了i到j有一条边,且权值为f[i][j]
第二种是邻接表。用一个结构体+head数组记录这条边的信息
1 struct node{ 2 int val,to,next; 3 //node存储第i条边的信息,val为第i条边的权值,to为第i条边的终 4 //点,next为同一起点的下一条边的第几条边数 5 }edge[2005]; 6 int head[2005];//head[x]表示以x节点为起点的第一条边的边数 7 int tot;//tot记录已经读到第几条边 8 9 /*建边*/ 10 void build(int x,int y,int z) 11 { 12 tot++; 13 edge[tot].val=z; 14 edge[tot].to=y; 15 edge[tot].next=head[x];//这里好像链表一样 16 head[x]=tot; 17 }
我们可以用这张图帮助理解。
、
长得有点像链表 是不是耶...
一、最暴力的弗洛伊德Floyd
1 //floyd 2 #include<bits/stdc++.h> 3 using namespace std; 4 const int inf=0x7f7f7f7f; 5 int n,m; 6 int w[2000][2000]; 7 int dis[2000][2000]; 8 int main() 9 { 10 scanf("%d%d",&n,&m); 11 for(int i=1;i<=n;i++) 12 { 13 for(int j=1;j<=n;j++) 14 dis[i][j]=inf; 15 } 16 for(int i=1;i<=m;i++) 17 { 18 int u,v; 19 scanf("%d%d%d",&u,&v,&w[u][v]); 20 dis[u][v]=w[u][v]; 21 } 22 //floyd 23 for(int k=1;k<=n;k++) 24 for(int i=1;i<=n;i++) 25 for(int j=1;j<=n;j++) 26 if(dis[i][j]>dis) 27 dis[i][j]=dis[i][k]+dis[k][j]; 28 //输出 dis[i][j]为i到j的最短路 29 return 0; 30 }
二、Dijkstra+堆优化+邻接表
1 // Dijkstra 2 #include<bits/stdc++.h> 3 using namespace std; 4 int n,m,s; 5 const int inf=0x3f3f3f3f; 6 int tot; 7 bool visit[500005]; 8 int head[500005],dis[500005]; 9 struct node{ 10 int val,to,next; 11 }edge[500005]; 12 priority_queue< pair<int,int> > q; 13 //大根堆 pair第一维为dis相反数以达到小根堆效果,第二维为节点编号 14 void add(int x,int y,int z) 15 { 16 tot++; 17 edge[tot].to=y; 18 edge[tot].val=z; 19 edge[tot].next=head[x]; 20 head[x]=tot; 21 } 22 void dijkstra() 23 { 24 for(int i=1;i<=n;i++) 25 dis[i]=inf; 26 dis[s]=0;//这里默认把1作为起点,求多源时可另加调整 27 q.push(make_pair(0,s)); 28 while(!q.empty()) 29 { 30 int x=q.top().second; 31 q.pop();//pop会把二元组中两个元素一并弹出 32 if(visit[x]) continue; 33 visit[x]=1; 34 for(int i=head[x];i;i=edge[i].next) 35 { 36 int y=edge[i].to; 37 int z=edge[i].val; 38 if(dis[y]>dis[x]+z&&visit[y]==0) 39 { 40 dis[y]=dis[x]+z; 41 q.push(make_pair(-dis[y],y)); 42 } 43 } 44 } 45 } 46 int main() 47 { 48 scanf("%d%d%d",&n,&m,&s); 49 for(int i=1;i<=m;i++) 50 { 51 int x=0,y=0,z=0; 52 scanf("%d%d%d",&x,&y,&z); 53 add(x,y,z); 54 } 55 dijkstra(); 56 for(int i=1;i<=n;i++) 57 printf("%d ",dis[i]); 58 return 0; 59 }
三、SPFA算法(大力拒绝bellmanford
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<queue> 6 using namespace std; 7 const int inf=2147483647; 8 int n,m,s; 9 int num; 10 int pre[20000]; 11 struct node{ 12 int to,val,next; 13 }edge[500005]; 14 int dis[20000]; 15 bool visit[20000]; 16 void add(int x,int y,int z) 17 { 18 num++; 19 edge[num].val=z; 20 edge[num].to=y; 21 edge[num].next=pre[x]; 22 pre[x]=num; 23 } 24 void spfa() 25 { 26 queue<int>q; 27 for(int i=1;i<=n;i++) 28 { 29 dis[i]=inf; 30 } 31 q.push(s); 32 dis[s]=0; 33 visit[s]=1; 34 while(!q.empty()) 35 { 36 int u=q.front(); 37 q.pop(); 38 visit[u]=0; 39 for(int i=pre[u];i>0;i=edge[i].next) 40 { 41 int v=edge[i].to; 42 if(dis[v]>dis[u]+edge[i].val) 43 { 44 dis[v]=dis[u]+edge[i].val; 45 if(visit[v]==0) 46 { 47 visit[v]=1; 48 q.push(v); 49 } 50 } 51 52 } 53 } 54 } 55 int main() 56 { 57 scanf("%d%d%d",&n,&m,&s); 58 for(int i=1;i<=m;i++) 59 { 60 int x=0,y=0,z=0; 61 scanf("%d%d%d",&x,&y,&z); 62 add(x,y,z); 63 } 64 spfa(); 65 for(int i=1;i<=n;i++) 66 { 67 if(s==i) cout<<"0"<<" "; 68 else cout<<dis[i]<<" "; 69 } 70 71 72 return 0; 73 }
最后我们再来讨论一下防止溢出的问题。
建邻接表的时候,edge开到边数,无向图开二倍边;
head dis visit均存到点数即可。
独立意志与自由思想是必须争的,且须以生死力争。