前言:
(摘自https://www.cnblogs.com/aininot260/p/9388103.html):
在最短路问题中,如果我们面对的是稠密图(十分稠密的那种,比如说全连接图),计算多源最短路的时候,Floyd算法才能充分发挥它的优势,彻彻底底打败SPFA和Dijkstra
在别的最短路问题中都不推荐使用这个算法
功能:求最短路径 ,求有向图的最小环或者最大环(顶点数>=2),求无向图的最小环(顶点数>=3)。
最短路径
//code by virtualtan 2019/2
1 #include2 #include 3 #define INF 200000000 4 #define MAX 10001 5 int n,m,s; 6 int dis[MAX][MAX]; 7 8 inline int read() 9 { 10 int x=0,k=1; char c=getchar(); 11 while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();} 12 while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); 13 return x*k; 14 }//快读 15 int main() { 16 17 n=read(),m=read(),s=read(); 18 for(int i = 1; i <= n; i++) { 19 for(int j = 1;j <= n; j++) { 20 dis[i][j] = INF;//先初始化为正无穷 21 } 22 } 23 for(int i = 1, x, y, val; i <= m; i++) { 24 scanf("%d%d%d",&x,&y,&val); 25 dis[x][y] = std::min(dis[x][y], val);//如果有边相连 //可以解决重边 26 }//用邻接矩阵存图 27 for(int k = 1; k <= n; k++) {//k为中介点,就是一个DP 28 for(int i = 1; i <= n; i++) {//i为起点,j为终点 29 if(i == k || dis[i][k] == INF) continue; 30 for(int j = 1;j <= n; j++) { 31 if(dis[i][j] > dis[i][k] + dis[k][j]) 32 dis[i][j] = dis[i][k] + dis[k][j]; 33 } 34 } 35 } 36 dis[s][s] = 0; 37 for(int i = 1; i <= n; i++) 38 if(i != s) printf("%d ",dis[s][i]); 39 else printf("0 "); 40 }
注:判断负环:如果存在u,使dis[u][u] < 0; 则存在负环
//参考代码&blog(实际上是比我写的好的多的东西)
https://ksmeow.moe/floyd_warshall/
注意:
dis[i][j]实际上是dis[k][i][j], 表示i到j的中间节点(不包括i,j)都在(1,k)时,i到j的最短路
而又因为每一层都是有上一层决定,不会受这一层影响,所以可以利用滚动数组优化内存空间,将k去除掉
打印路径:
传送
传递闭包
在有向图中,有时不用关心路径的长度,而只关心两点间是否有通路,则可以用“1” 表示联通, “0”表示不联通,这样预处理少许修改后,再把主程序中改成
1 d[i][j] = d[i][j] || (d[i][k] && d[i][k]);
这样的结果称为有向图的传递闭包
运用例题:https://vjudge.net/problem/UVA-247
1 #include2 #include
最小环:
参考博客:https://blog.csdn.net/qq_36386435/article/details/77403223
https://www.cnblogs.com/zzqc/p/6855913.html
https://blog.csdn.net/yo_bc/article/details/75042688
无向图的最小环:
(最大环略......)
板子: 传送门
1 #include2 using namespace std; 3 const int INF = 99999999;//切记别爆 inf*3即可//程序可能出现3个inf相加 4 const int MAX = 100+9; 5 6 int n,m; 7 int dis[MAX][MAX]; 8 int e[MAX][MAX]; 9 10 11 int main() { 12 while(cin>>n>>m) {//吐槽一下:杭电的OJ这里要写while(~scanf("%d%d",&n,&m) ) 13 for(int i = 1; i <= n; i++) { 14 for(int j = 1; j <= n; j++) { 15 if(i == j) e[i][i] = dis[i][i] = 0; 16 else e[i][j] = dis[i][j] = INF; 17 } 18 } 19 int a,b,c; 20 for(int i = 1; i <= m; i++) { 21 scanf("%d%d%d",&a,&b,&c); 22 if(c < e[a][b]) //有可能重复输入某些边,但它权值又不一样 23 e[a][b]=e[b][a] = dis[a][b]=dis[b][a] = c; 24 //多一个 e数组 的作用在于此: 在松弛的过程中,会破坏掉两点之间是否真的存在边的表示,所有需要多开一个 e 25 //没有真的存在边的话,e就会是INF 26 } 27 28 int ans = INF; 29 /* 外层循环 k 用于更新最小环ans */ 30 for(int k = 1; k <= n; k++) { 31 /* 先判断最小环(也就是第一个for),再更新最短路(第二个for)的原因: 32 会出现重边现象,所以一个环至少有三个点,所以每次先判最小环 33 因为k必须是未用过的,此时的dis[i][j]是遍历了k-1次的最短路,用完判断了再去更新k对应的最短路。 34 每次比较dist[i][j]+ e[i][k]+e[k][j]的最小值。k—>i———>j—>k;这样一直保证环是三个点。??? 35 */ 36 for(int i = 1; i < k; i++) {//关于这里的"i 37 //原因不知道是不是这个(希望有人可以指点指点): 38 /* i循环到k-1的原因: 39 举例:1->3 ,3->2(只有两条边) 的一个图 40 已经用3把dis[1][2]给松弛了,再利用3来求最小环时,算出的ans=dis[1][2]+e[1][3]+e[3][2] 41 这显然不是一个环...所以我们第一个for里i,j都是只循环到k-1 42 这就是重边 ??? 43 */ 44 45 for(int j = i+1; j < k; j++) { 46 /*j从i+1开始是因为无向图的对称性质 ??? 47 */ 48 ans = min(ans , dis[i][j] + e[i][k] + e[k][j]); 49 } 50 } 51 52 for(int i = 1; i <= n; i++) {//松弛操作 更新最短路 53 for(int j = 1; j <= n; j++) { 54 dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]); 55 } 56 } 57 } 58 if(ans == INF) printf("It's impossible.\n"); 59 else printf("%d\n",ans); 60 } 61 return 0; 62 63 }
这个环为:j,k,i...j
有向图的最小环:
对于上面代码第43行部分,这个j之所以从i+1开始就可以了是因为无向图的对称性质,而有向图并不具有这个性质,所以需要改动.
仔细想一想,有向图的最小环其实只要直接跑一遍floyd,然后遍历一遍寻找最小的dis[i][i]即可(所以连dis[i][i] 一起都要初始化为INF哦),因为图是无向的所以不必担心出现重边啊
例题:传送门
1 #include2 using namespace std; 3 const int MAX = 200+9; 4 const int INF = 0x3f3f3f3f; 5 6 int n,m; 7 int w[MAX];//w[i]表示弯道i的时间 8 int e[MAX][MAX]; /*e[i][j]表示 弯道i到弯道j的最小直道时间 与 起点i的时间和 9 这样就转化为一个普通的图了(节点无权值)*/ 10 11 int main() { 12 scanf("%d%d",&n,&m); 13 for(int i = 1; i <= n; i++) { 14 scanf("%d",&w[i]); 15 } 16 for(int i = 1; i <= n; i++) { 17 for(int j = 1; j <= n; j++) { 18 e[i][j] = INF; 19 } 20 }//貌似这题不用这个也行 21 int a,b,c; 22 for(int i = 1; i <= m; i++) { 23 scanf("%d%d%d",&a,&b,&c); 24 e[a][b] = min(e[a][b],c+w[a]); 25 } 26 for(int k = 1; k <= n; k++) { 27 for(int i = 1; i <= n; i++) { 28 for(int j = 1; j <= n; j++) { 29 e[i][j] = min(e[i][j],e[i][k]+e[k][j]); 30 } 31 } 32 } 33 // int ans = INF; 34 // for(int i = 1; i <= n; i++) ans = min(ans,e[i][i]) ; 35 // if(ans==INF) ans = -1 ; //题目要求:必须经过1号弯道 36 printf("%d",e[1][1]==INF?-1:e[1][1]); 37 return 0; 38 }
其他运用:
一个小运用:
POJ3660 Cow Conte http://poj.org/problem?id=3660
1 #include2 //题目分析:如果 奶牛能力确定,则赢它的奶牛数 + 输给它奶牛数 == n - 1 3 #define MAX 5555 4 bool a[MAX][MAX]; 5 //a[x][y] == 1 表示 x 与 y 比赛,x胜 6 int b[MAX], c[MAX]; 7 //c[i] 表示第i个奶牛赢过的奶牛数 , b[j] 表示输的 8 int n,m,ans; 9 10 int main() { 11 scanf("%d%d",&n,&m); 12 //初始化 13 for(int i = 1, x, y; i <= m; i++) { 14 scanf("%d%d",&x,&y); 15 a[x][y] = 1; 16 b[x]++,c[y]++; 17 } 18 for(int k = 1; k <= n; k++) {//用floyd枚举中间点 19 for(int i = 1; i <= n; i++) { 20 for(int j = 1; j <= n; j++) { 21 if(a[i][j] == 0 && (a[i][k] == 1) && (a[k][j] == 1) ) { 22 // 如果 i 比 k 厉害,k 比 j 厉害, i 自然 比 j 厉害 23 //当 i 和 j 没有 比过赛&& i 比 j厉害,通过枚举中间点,可以确定这个中间点k的b[k],c[k] 24 a[i][j] = 1; 25 b[i]++; 26 c[j]++; 27 } 28 } 29 } 30 } 31 for(int i = 1; i <= n; i++) if(b[i] + c[i] == n - 1) ans++; 32 printf("%d",ans); 33 }
收获
floyd 可以 确定两点之间的大小关系(通过枚举中间点)
不过要注意条件
此题 还行