您的位置:首页 > 其它

floyd最小环 详细讲解

2015-11-19 15:31 330 查看
转载自:http://m.blog.csdn.net/blog/qq909157370/9225109#

hdu 1599 find the mincost route(找无向图最小环)

注意!这里写成 #define data 0x3f3f3f3f memset(map,data,sizeof(map))是wrong 按理来说应该不错,郁闷,以后还是循环赋值然后宏定义#define data 100000000

Problem Description

杭州有N个景区,景区之间有一些双向的路来连接,现在8600想找一条旅游路线,这个路线从A点出发并且最后回到A点,假设经过的路线为V1,V2,....VK,V1,那么必须满足K>2,就是说至除了出发点以外至少要经过2个其他不同的景区,而且不能重复经过同一个景区。现在8600需要你帮他找一条这样的路线,并且花费越少越好。

Input

第一行是2个整数N和M(N <= 100, M <= 1000),代表景区的个数和道路的条数。

接下来的M行里,每行包括3个整数a,b,c.代表a和b之间有一条通路,并且需要花费c元(c <= 100)。

Output

对于每个测试实例,如果能找到这样一条路线的话,输出花费的最小值。如果找不到的话,输出"It's impossible.".

Sample Input

3 3 1 2 1 2 3 1 1 3 1 3 3 1 2 1 1 2 3 2 3 1

Sample Output

3 It's impossible.

对于找最小环,而且要经过至少两个节点,权值和最小,算法是floyd,但该注意和理解的地方实在很多

1.定义和理解:转自http://leon.cc.blogbus.com/logs/3629782.html

在图论中经常会遇到这样的问题,在一个有向图里,求出任意两个节点之间的最短距离。我们在离散数学、数据结构课上都遇到过这个问题,在计算机网络里介绍网络层的时候好像也遇到过这个问题,记不请了... 但是书本上一律采取的是Dijkstra算法,通过Dijkstra算法可以求出单源最短路径,然后逐个节点利用Dijkstra算法就可以了。不过在这里想换换口味,采取Robert Floyd提出的算法来解决这个问题。下面让我们先把问题稍微的形式化一下:

如果有一个矩阵D=[d(ij)],其中d(ij)>0表示i城市到j城市的距离。若i与j之间无路可通,那么d(ij)就是无穷大。又有d(ii)=0。编写一个程序,通过这个距离矩阵D,把任意两个城市之间的最短与其行径的路径找出来。

我们可以将问题分解,先找出最短的距离,然后在考虑如何找出对应的行进路线。如何找出最短路径呢,这里还是用到动态规划的知识,对于任何一个城市而言,i到j的最短距离不外乎存在经过i与j之间的k和不经过k两种可能,所以可以令k=1,2,3,...,n(n是城市的数目),在检查d(ij)与d(ik)+d(kj)的值;在此d(ik)与d(kj)分别是目前为止所知道的i到k与k到j的最短距离,因此d(ik)+d(kj)就是i到j经过k的最短距离。所以,若有d(ij)>d(ik)+d(kj),就表示从i出发经过k再到j的距离要比原来的i到j距离短,自然把i到j的d(ij)重写为d(ik)+d(kj),每当一个k查完了,d(ij)就是目前的i到j的最短距离。重复这一过程,最后当查完所有的k时,d(ij)里面存放的就是i到j之间的最短距离了。所以我们就可以用三个for循环把问题搞定了,但是有一个问题需要注意,那就是for循环的嵌套的顺序:我们可能随手就会写出这样的程序,但是仔细考虑的话,会发现是有问题的。

for(int i=0; i<n; i++)

for(int j=0; j<n; j++)

for(int k=0; k<n; k++)

问题出在我们太早的把i-k-j的距离确定下来了,假设一旦找到了i-p-j最短的距离后,i到j就相当处理完了,以后不会在改变了,一旦以后有使i到j的更短的距离时也不能再去更新了,所以结果一定是不对的。所以应当象下面一样来写程序:

for(int k=0; k<n; k++)

for(int i=0; i<n; i++)

for(int j=0; j<n; j++)

这样作的意义在于固定了k,把所有i到j而经过k的距离找出来,然后象开头所提到的那样进行比较和重写,因为k是在最外层的,所以会把所有的i到j都处理完后,才会移动到下一个k,这样就不会有问题了,看来多层循环的时候,我们一定要当心,否则很容易就弄错了。

接下来就要看一看如何找出最短路径所行经的城市了,这里要用到另一个矩阵P,它的定义是这样的:p(ij)的值如果为p,就表示i到j的最短行经为i->...->p->j,也就是说p是i到j的最短行径中的j之前的最后一个城市。P矩阵的初值为p(ij)=i。有了这个矩阵之后,要找最短路径就轻而易举了。对于i到j而言找出p(ij),令为p,就知道了路径i->...->p->j;再去找p(ip),如果值为q,i到p的最短路径为i->...->q->p;再去找p(iq),如果值为r,i到q的最短路径为i->...->r->q;所以一再反复,到了某个p(it)的值为i时,就表示i到t的最短路径为i->t,就会的到答案了,i到j的最短行径为i->t->...->q->p->j。因为上述的算法是从终点到起点的顺序找出来的,所以输出的时候要把它倒过来。

但是,如何动态的回填P矩阵的值呢?回想一下,当d(ij)>d(ik)+d(kj)时,就要让i到j的最短路径改为走i->...->k->...->j这一条路,但是d(kj)的值是已知的,换句话说,就是k->...->j这条路是已知的,所以k->...->j这条路上j的上一个城市(即p(kj))也是已知的,当然,因为要改走i->...->k->...->j这一条路,j的上一个城市正好是p(kj)。所以一旦发现d(ij)>d(ik)+d(kj),就把p(kj)存入p(ij)。

2 对于代码的理解:

[cpp] view plaincopyprint?

1 #include<iostream>

2 #include<cstdio>

3 #include<cstring>

4 using namespace std;

5 #define data 100000000

6 #define N 110

7 #define min(x,y) ((x)>(y)?(y):(x))

8 int map

,dis

;

9 int n,m;

10 void floyd()

11 {

12 int i,j,k,mina=data;

13 for(k=1;k<=n;k++)

14 {

15 //因为路径i到j的情况只有经过k和不经过k,而要求从一个点至少经过两个节点返回原点,k每次更新都会使dis[i][j]得到更新,而只有在更新了一次k之后才可以找min,min即是在dis[i][i]最短的情况下的求至少经过两个点又回到该点的最小距离,所以i和j的值都应该小于k,i的值从1到k-1,而j的值却跟i的值相关,即i!=j,因为当i=j时,dis[i][j]不是无穷大,而是从i->j->i的值,这就会出现自环,这里我定义自环为经过一个节点就返回原节点的节点,比如像1->2->1这样min的值会不准确,这不是经过了两个节点,所以下面第一个两层循环可以有三种写法,具体看代码

16

17 //当要扩充第k个节点时,前k-1个节点已经用过,并且是用于更新最短路径dis[i][j]这就是第二个两层for循环,所以在更新k之前已经有一条最短路径从i到达j,此时再来寻找另外一个从i到j的路径,map[j][k]+map[k][i],如果有的话则一定形成了从i回到i的环,比如 1->2值为1,2->3值为2,3->4值为3,4->1值为4,则第一次存在从1到3的最短路,再寻找时找到了1到4,4到3的路径,则形成了环,而且是最小的,注意第一个循环中加上的值是map[j][k]和map[k][i]的值,map的是值都是初始值,不会变化,而dis在不断更新

18 for(i=1;i<k;i++)

19 {

20 for(j=i+1;j<k;j++)

21 {

22 mina=min(dis[i][j]+map[j][k]+map[k][i],mina);

23 }

24 }

25 for(i=1;i<=n;i++)

26 {

27 for(j=1;j<=n;j++)

28 {

29 if(dis[i][j]>(dis[i][k]+dis[k][j]))

30 dis[i][j]=dis[i][k]+dis[k][j];

31 }

32 }

33 }

34 if(mina<data)

35 printf("%d\n",mina);

36 else

37 printf("It's impossible.\n");

38 }

39 int main()

40 {

41 int a,b,c,i,j;

42 while(scanf("%d%d",&n,&m)!=EOF)

43 {

44 for(i=0;i<=n;i++)

45 for(j=0;j<=n;j++)

46 {

47 map[i][j]=data;

48 dis[i][j]=data;

49 }

50 for(i=0;i<m;i++)

51 {

52 scanf("%d%d%d",&a,&b,&c);

53 if(map[a]>c)

54 {

55 map[a][b]=map[b][a]=c;

56 dis[a][b]=dis[b][a]=c;

57 }

58 }

59 floyd();

60 }

61 [b]return
0;

62 }

这是第二种:

[cpp] view plaincopyprint?

63 #include<iostream>

64 #include<cstdio>

65 #include<cstring>

66 using namespace std;

67 #define data 100000000

68 #define N 110

69 #define min(x,y) ((x)>(y)?(y):(x))

70 int map

,dis

;

71 int n,m;

72 void floyd()

73 {

74 int i,j,k,mina=data;

75 for(k=1;k<=n;k++)

76 {

77 for(i=1;i<k;i++)

78 {

79 for(j=1;j<i;j++)

80 {

81 mina=min(dis[i][j]+map[j][k]+map[k][i],mina);

82 }

83 }

84 for(i=1;i<=n;i++)

85 {

86 for(j=1;j<=n;j++)

87 {

88 if(dis[i][j]>(dis[i][k]+dis[k][j]))

89 dis[i][j]=dis[i][k]+dis[k][j];

90 }

91 }

92 }

93 if(mina<data)

94 printf("%d\n",mina);

95 else

96 printf("It's impossible.\n");

97 }

98 int main()

99 {

100 int a,b,c,i,j;

101 while(scanf("%d%d",&n,&m)!=EOF)

102 {

103 for(i=0;i<=n;i++)

104 for(j=0;j<=n;j++)

105 {

106 map[i][j]=data;

107 dis[i][j]=data;

108 }

109 for(i=0;i<m;i++)

110 {

111 scanf("%d%d%d",&a,&b,&c);

112 if(map[a]>c)

113 {

114 map[a][b]=map[b][a]=c;

115 dis[a][b]=dis[b][a]=c;

116 }

117 }

118 floyd();

119 }

120 [b]return
0;

121 }

这是第三种:

[cpp] view plaincopyprint?

122 #include<iostream>

123 #include<cstdio>

124 #include<cstring>

125 using namespace std;

126 #define data 100000000

127 #define N 110

128 #define min(x,y) ((x)>(y)?(y):(x))

129 int map

,dis

;

130 int n,m;

131 void floyd()

132 {

133 int i,j,k,mina=data;

134 for(k=1;k<=n;k++)

135 {

136 for(i=1;i<k;i++)

137 {

138 for(j=1;j<k;j++)

139 {

140 mina=min(dis[i][j]+map[j][k]+map[k][i],mina);

141 }

142 }

143 for(i=1;i<=n;i++)

144 {

145 for(j=1;j<=n;j++)

146 {

147 //注意这里i!=j

148 if(i!=j&&dis[i][j]>(dis[i][k]+dis[k][j]))

149 dis[i][j]=dis[i][k]+dis[k][j];

150 }

151 }

152 }

153 if(mina<data)

154 printf("%d\n",mina);

155 else

156 printf("It's impossible.\n");

157 }

158 int main()

159 {

160 int a,b,c,i,j;

161 while(scanf("%d%d",&n,&m)!=EOF)

162 {

163 for(i=0;i<=n;i++)

164 for(j=0;j<=n;j++)

165 {

166 map[i][j]=data;

167 dis[i][j]=data;

168 }

169 for(i=0;i<m;i++)

170 {

171 scanf("%d%d%d",&a,&b,&c);

172 if(map[a][b]>c)

173 {

174 map[a][b]=map[b][a]=c;

175 dis[a][b]=dis[b][a]=c;

176 }

177 }

Floyd算法

正如我们所知道的,Floyd算法用于求最短路径。Floyd算法可以说是Warshall算法的扩展,三个for循环就可以解决问题,所以它的时间复杂度为O(n^3)。

Floyd算法的基本思想如下:从任意节点A到任意节点B的最短路径不外乎2种可能,1是直接从A到B,2是从A经过若干个节点X到B。所以,我们假设Dis(AB)为节点A到节点B的最短路径的距离,对于每一个节点X,我们检查Dis(AX) + Dis(XB) < Dis(AB)是否成立,如果成立,证明从A到X再到B的路径比A直接到B的路径短,我们便设置Dis(AB) = Dis(AX) + Dis(XB),这样一来,当我们遍历完所有节点X,Dis(AB)中记录的便是A到B的最短路径的距离。

很简单吧,代码看起来可能像下面这样:

?
for(inti = 0; i < 节点个数; ++i )

{

for(intj = 0; j < 节点个数; ++j )

{

for(intk = 0; k < 节点个数; ++k )

{

if( Dis[i][k] + Dis[k][j] < Dis[i][j] )

{

// 找到更短路径

Dis[i][j] = Dis[i][k] + Dis[k][j];

}

}

}

}

但是这里我们要注意循环的嵌套顺序,如果把检查所有节点X放在最内层,那么结果将是不正确的,为什么呢?因为这样便过早的把i到j的最短路径确定下来了,而当后面存在更短的路径时,已经不再会更新了。

让我们来看一个例子,看下图:



图中红色的数字代表边的权重。如果我们在最内层检查所有节点X,那么对于A->B,我们只能发现一条路径,就是A->B,路径距离为9。而这显然是不正确的,真实的最短路径是A->D->C->B,路径距离为6。造成错误的原因就是我们把检查所有节点X放在最内层,造成过早的把A到B的最短路径确定下来了,当确定A->B的最短路径时Dis(AC)尚未被计算。所以,我们需要改写循环顺序,如下:

?
for(intk = 0; k < 节点个数; ++k )

{

for(inti = 0; i < 节点个数; ++i )

{

for(intj = 0; j < 节点个数; ++j )

{

if( Dis[i][k] + Dis[k][j] < Dis[i][j] )

{

// 找到更短路径

Dis[i][j] = Dis[i][k] + Dis[k][j];

}

}

}

}

这样一来,对于每一个节点X,我们都会把所有的i到j处理完毕后才继续检查下一个节点。

那么接下来的问题就是,我们如何找出最短路径呢?这里需要借助一个辅助数组Path,它是这样使用的:Path(AB)的值如果为P,则表示A节点到B节点的最短路径是A->...->P->B。这样一来,假设我们要找A->B的最短路径,那么就依次查找,假设Path(AB)的值为P,那么接着查找Path(AP),假设Path(AP)的值为L,那么接着查找Path(AL),假设Path(AL)的值为A,则查找结束,最短路径为A->L->P->B。

那么,如何填充Path的值呢?很简单,当我们发现Dis(AX) + Dis(XB) < Dis(AB)成立时,就要把最短路径改为A->...->X->...->B,而此时,Path(XB)的值是已知的,所以,Path(AB) = Path(XB)。

好了,基本的介绍完成了,接下来就是实现的时候了,这里我们使用图以及邻接矩阵:

?
#define INFINITE 1000 // 最大值

#define MAX_VERTEX_COUNT 20   // 最大顶点个数

//////////////////////////////////////////////////////////////////////////

structGraph

{

intarrArcs[MAX_VERTEX_COUNT][MAX_VERTEX_COUNT];// 邻接矩阵

intnVertexCount;   // 顶点数量

intnArcCount;   // 边的数量

};

//////////////////////////////////////////////////////////////////////////

首先,我们写一个方法,用于读入图的数据:

?
voidreadGraphData( Graph *_pGraph )

{

std::cout <<"请输入顶点数量和边的数量: ";

std::cin >> _pGraph->nVertexCount;

std::cin >> _pGraph->nArcCount;

std::cout <<"请输入邻接矩阵数据:"<< std::endl;

for(introw = 0; row < _pGraph->nVertexCount; ++row )

{

for(intcol = 0; col < _pGraph->nVertexCount; ++col )

{

std::cin >> _pGraph->arrArcs[row][col];

}

}

}

接着,就是核心的Floyd算法:

?
voidfloyd(int_arrDis[][MAX_VERTEX_COUNT],int_arrPath[][MAX_VERTEX_COUNT],int_nVertexCount )

{

// 先初始化_arrPath

for(inti = 0; i < _nVertexCount; ++i )

{

for(intj = 0; j < _nVertexCount; ++j )

{

_arrPath[i][j] = i;

}

}

//////////////////////////////////////////////////////////////////////////

for(intk = 0; k < _nVertexCount; ++k )

{

for(inti = 0; i < _nVertexCount; ++i )

{

for(intj = 0; j < _nVertexCount; ++j )

{

if( _arrDis[i][k] + _arrDis[k][j] < _arrDis[i][j] )

{

// 找到更短路径

_arrDis[i][j] = _arrDis[i][k] + _arrDis[k][j];

_arrPath[i][j] = _arrPath[k][j];

}

}

}

}

}

OK,最后是输出结果数据代码:

?
voidprintResult(int_arrDis[][MAX_VERTEX_COUNT],int_arrPath[][MAX_VERTEX_COUNT],int_nVertexCount )

{

std::cout <<"Origin -> Dest Distance Path"<< std::endl;

for(inti = 0; i < _nVertexCount; ++i )

{

for(intj = 0; j < _nVertexCount; ++j )

{

if( i != j )// 节点不是自身

{

std::cout << i+1 <<" -> "<< j+1 <<"\t\t";

if( INFINITE == _arrDis[i][j] )// i -> j 不存在路径

{

std::cout <<"INFINITE"<<"\t\t";

}

else

{

std::cout << _arrDis[i][j] <<"\t\t";

// 由于我们查询最短路径是从后往前插,因此我们把查询得到的节点

// 压入栈中,最后弹出以顺序输出结果。

std::stack<int> stackVertices;

intk = j;

do

{

k = _arrPath[i][k];

stackVertices.push( k );

}while( k != i );

//////////////////////////////////////////////////////////////////////////

std::cout << stackVertices.top()+1;

stackVertices.pop();

unsignedintnLength = stackVertices.size();

for( unsignedintnIndex = 0; nIndex < nLength; ++nIndex )

{

std::cout <<" -> "<< stackVertices.top()+1;

stackVertices.pop();

}

std::cout <<" -> "<< j+1 << std::endl;

}

}

}

}

}

好了,是时候测试了,我们用的图如下:



测试代码如下:

?
intmain(void)

{

Graph myGraph;

readGraphData( &myGraph );

//////////////////////////////////////////////////////////////////////////

intarrDis[MAX_VERTEX_COUNT][MAX_VERTEX_COUNT];

intarrPath[MAX_VERTEX_COUNT][MAX_VERTEX_COUNT];

// 先初始化arrDis

for(inti = 0; i < myGraph.nVertexCount; ++i )

{

for(intj = 0; j < myGraph.nVertexCount; ++j )

{

arrDis[i][j] = myGraph.arrArcs[i][j];

}

}

floyd( arrDis, arrPath, myGraph.nVertexCount );

//////////////////////////////////////////////////////////////////////////

printResult( arrDis, arrPath, myGraph.nVertexCount );

//////////////////////////////////////////////////////////////////////////

system("pause");

return0;

}

如图:



#include<stdio.h>

#define inf 99999999

#define Min(a,b)((a)<(b))?(a):(b)

int n,m,min;

int d[102][102];

int a[102][102];

void floyd()

{

int i,k,j;

min=inf;

for(k=1;k<=n;k++)

{

for(i=1;i<k;i++)

for(j=1;j<i;j++)

min=Min(min,d[i][j]+a[i][k]+a[k][j]);

for(i=1;i<=n;i++)

for(j=1;j<=n;j++)

d[i][j]=Min(d[i][j],d[i][k]+d[k][j]);

}

}

int main()

{

int p,q,i,j,len;

while(scanf("%d%d",&n,&m)!=EOF)

{

for(i=1;i<=n;i++)

for(j=1;j<=n;j++)

a[i][j]=inf;

for(i=1;i<=m;i++)

{

scanf("%d%d%d",&p,&q,&len);

if(len<a[p][q])

a[p][q]=a[q][p]=len;

}

for(i=1;i<=n;i++)

for(j=1;j<=n;j++)

d[i][j]=a[i][j];

floyd();

if(min!=inf)

printf("%d\n",min);

else

printf("It's impossible.\n");

}

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: