【最短路径:这是我在这次学习图论的这个部分的第二个点,第一个点是判断是否有负权回路,不过还没来得及把分析和总结再温习一遍,那么今天就先把这个刚想通的题先写出来】
题目:求一个图中的最小环,并求路径。
分析:我在网上了看了好多人的分析,都是比较简洁的,大家也都一语道破这道题肯定是用floyd算法来解决。所以在此,我重点地谈谈遇到这道题的分析过程。
首先,这个问题比较关键的字眼,应该就是“最小环”,要求出最小,一般是要枚举出来所有环。但是,如果把最有环的枚举出来,那么必定超时啊,所以 我们可以找一些比较小的环。那么什么样的环是比较小的。
如果在一条最短路径之外,找一个能把最短路径首尾相连的点加入到这条路径中,那么就构成了一个相对较小的环!那么把所有这样的情况都找出来,就找出了所有比较小的环。(明确一点,floyd算法中所求的最短路径都是从个点到其他的点的最短路径,不包括本身,因此路径是一条简单路径)
这就是解题的关键点一。
其二,怎么去找最短路径之外的那些点?
这个就很巧妙了,我们反过来想。首先,我们已经分析到要用一个最短路径,再加一个点来构成一个较小环,而且这个完全足够用来找出所有的较小的环了。
floyd算法,其思想是我觉得可以这样理解,在两个点之间的路径中试图插入第三个点使得两点间的路径权值减小,从而遍历所有节点,把所有节点一次按照松弛算法插入(或不插入)到点到点的最短路径中。
那么一般都是用一个三层循环来做的,最外层的循环,就是用来遍历上述的第三个点。那么我们就可以想到,能不能在插入这点(到i和j之间的最短路径上)之前,把这点作为i到j的最短路径之外的点呢?
没错,这样是可以实现的。这里还要明确一点,序号小于第三个点的节点之间的路径,在没有插入第三个节点之前所求出的当前的最短路径上,是一定不存在k这个点的;但是其他点对之间就不一定了。也就是说这个中间点的序号,是所表示的这个环中的点的最大序号。所以对于每个中间点,枚举它两端的点(序号小于它),就可以找出以k为最大节点的所有较小的环。那么在这里,较小环就是指,
在没有插入k的时候的最短路径再加上k。这和前面说的较小环有些出路,但是主要的是这样的思想。
求路径:我们可以用一个二维数组pre[i][j]来存储j的前一个点,用递归来求路径。
总结:
利用floyd算法,求中间点k没有插入的时候,比k小的点之间的最短路径。既是floyd的扩展,也是floyd的应用。
代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define find_min(a, b) a > b ? a : b
const int N = 101;
const int INF = 0x7ffffff;
int mat[N][N], pre[N][N], dist[N][N], path[N], n, m, ans, cnt;
void init()
{
ans = INF;
for ( int i = 1; i <= n; ++i )
for ( int j = 1; j <= n; ++j ) {
if ( i == j ) dist[i][j] = mat[i][j] = 0;
else dist[i][j] = mat[i][j] = INF;
pre[i][j] = -1;
}
}
void findPath( int i, int j ) {
if ( i != j ) findPath ( i, pre[i][j] );
path[cnt++] = j;
}
void Floyd(){
for ( int k = 1; k <= n; ++k ) {
for ( int i = 1; i < k; ++i )
for ( int j = i + 1; j < k; ++j ) {
if ( ans > dist[i][j] + mat[i][k] + mat[k][j] ) {
ans = dist[i][j] + mat[i][k] + mat[k][j];
cnt = 0;
findPath( i, j );
path[cnt++] = k;
}
}
for ( int i = 1; i <= n; ++i )
for ( int j = 1; j <= n; ++j ) {
if ( dist[i][j] > dist[i][k] + dist[k][j] ) {
dist[i][j] = dist[i][k] + dist[k][j];
pre[i][j] = pre[k][j];
}
}
}
}
int main()
{
while ( scanf("%d%d", &n, &m) != EOF ) {
init();
for ( int i = 1; i <= m; ++i ) {
int a, b, cost;
scanf("%d%d%d", &a, &b, &cost);
mat[a][b] = mat[b][a] = dist[a][b] = dist[b][a] = ( dist[a][b] < cost ? dist[a][b] : cost );
pre[a][b] = a, pre[b][a] = b;
}
Floyd();
if ( ans == INF ) {
printf("No solution.\n");
continue;
}
printf("%d", path[0]);
for ( int i = 1; i < cnt; ++i ) printf(" %d", path[i]);
printf("\n");
}
}
我觉得用什么算法都是分析的结果,最重要的不是你在知道用什么算法的基础上解决了什么问题,我认为最重要的是,给你一道题,你知道如何去分析题目需要什么样的算法,或者是这道题属于哪个算法的范畴,并选择最合适的算法去解决问题。