找无向图最小环 floyd算法

 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个整数NMN <= 100, M <= 1000),代表景区的个数和道路的条数。
接下来的M行里,每行包括3个整数a,b,c.代表ab之间有一条通路,并且需要花费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城市的距离。若ij之间无路可通,那么d(ij)就是无穷大。又有d(ii)=0。编写一个程序,通过这个距离矩阵D,把任意两个城市之间的最短与其行径的路径找出来。
     我们可以将问题分解,先找出最短的距离,然后在考虑如何找出对应的行进路线。如何找出最短路径呢,这里还是用到动态规划的知识,对于任何一个城市而言,ij的最短距离不外乎存在经过ij之间的k和不经过k两种可能,所以可以令k=123...n(n是城市的数目),在检查d(ij)d(ik)+d(kj)的值;在此d(ik)d(kj)分别是目前为止所知道的ikkj的最短距离,因此d(ik)+d(kj)就是ij经过k的最短距离。所以,若有d(ij)>d(ik)+d(kj),就表示从i出发经过k再到j的距离要比原来的ij距离短,自然把ijd(ij)重写为d(ik)+d(kj)每当一个k查完了,d(ij)就是目前的ij的最短距离。重复这一过程,最后当查完所有的k时,d(ij)里面存放的就是ij之间的最短距离了。所以我们就可以用三个for循环把问题搞定了,但是有一个问题需要注意,那就是for循环的嵌套的顺序:我们可能随手就会写出这样的程序,但是仔细考虑的话,会发现是有问题的。

                     for(int i=0; i
                           for(int j=0; j
                                for(int k=0; k
    

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

                   for(int k=0; k
                         for(int i=0; i
                              for(int j=0; j

    这样作的意义在于固定了k,把所有ij而经过k的距离找出来,然后象开头所提到的那样进行比较和重写,因为k是在最外层的,所以会把所有的ij都处理完后,才会移动到下一个k,这样就不会有问题了,看来多层循环的时候,我们一定要当心,否则很容易就弄错了。
     接下来就要看一看如何找出最短路径所行经的城市了,这里要用到另一个矩阵P,它的定义是这样的:p(ij)的值如果为p,就表示ij的最短行经为i->...->p->j,也就是说pij的最短行径中的j之前的最后一个城市。P矩阵的初值为p(ij)=i。有了这个矩阵之后,要找最短路径就轻而易举了。对于ij而言找出p(ij),令为p,就知道了路径i->...->p->j;再去找p(ip),如果值为qip的最短路径为i->...->q->p;再去找p(iq),如果值为riq的最短路径为i->...->r->q;所以一再反复,到了某个p(it)的值为i时,就表示it的最短路径为i->t,就会的到答案了,ij的最短行径为i->t->...->q->p->j。因为上述的算法是从终点到起点的顺序找出来的,所以输出的时候要把它倒过来。
     但是,如何动态的回填P矩阵的值呢?回想一下,当d(ij)>d(ik)+d(kj)时,就要让ij的最短路径改为走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?

#include  

#include  

#include  

using namespace std;  

#define data 100000000  

#define N 110  

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

int map[N][N],dis[N][N];  

int n,m;  

10 void floyd()  

11 {  

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

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

14     {  

15     //因为路径ij的情况只有经过k和不经过k,而要求从一个点至少经过两个节点返回原点,k每次更新都会使dis[i][j]得到更新,而只有在更新了一次k之后才可以找minmin即是在dis[i][i]最短的情况下的求至少经过两个点又回到该点的最小距离,所以ij的值都应该小于ki的值从1k-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,此时再来寻找另外一个从ij的路径,map[j][k]+map[k][i],如果有的话则一定形成了从i回到i的环,比如 1->2值为12->3值为2,3->4值为3,4->1值为4,则第一次存在从13的最短路,再寻找时找到了14,43的路径,则形成了环,而且是最小的,注意第一个循环中加上的值是map[j][k]map[k][i]的值,map的是值都是初始值,不会变化,而dis在不断更新  

18         for(i=1;i

19         {  

20             for(j=i+1;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

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

51         {  

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

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

54             {  

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

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

57             }  

58         }  

59         floyd();  

60     }  

61     return 0;  

62 }  


这是第二种:

[cpp] view plaincopyprint?

63 #include  

64 #include  

65 #include  

66 using namespace std;  

67 #define data 100000000  

68 #define N 110  

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

70 int map[N][N],dis[N][N];  

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

78         {  

79             for(j=1;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

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

110         {  

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

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

113             {  

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

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

116             }  

117         }  

118         floyd();  

119     }  

120     return 0;  

121 }  


这是第三种:

[cpp] view plaincopyprint?

122 #include  

123 #include  

124 #include  

125 using namespace std;  

126 #define data 100000000  

127 #define N 110  

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

129 int map[N][N],dis[N][N];  

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

137         {  

138             for(j=1;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

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

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是直接从AB2是从A经过若干个节点XB。所以,我们假设Dis(AB)为节点A到节点B的最短路径的距离,对于每一个节点X,我们检查Dis(AX) + Dis(XB) < Dis(AB)是否成立,如果成立,证明从AX再到B的路径比A直接到B的路径短,我们便设置Dis(AB) = Dis(AX) + Dis(XB),这样一来,当我们遍历完所有节点XDis(AB)中记录的便是AB的最短路径的距离。

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

?

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放在最内层,那么结果将是不正确的,为什么呢?因为这样便过早的把ij的最短路径确定下来了,而当后面存在更短的路径时,已经不再会更新了。

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

图中红色的数字代表边的权重。如果我们在最内层检查所有节点X,那么对于A->B,我们只能发现一条路径,就是A->B,路径距离为9。而这显然是不正确的,真实的最短路径是A->D->C->B,路径距离为6。造成错误的原因就是我们把检查所有节点X放在最内层,造成过早的把AB的最短路径确定下来了,当确定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,我们都会把所有的ij处理完毕后才继续检查下一个节点。

那么接下来的问题就是,我们如何找出最短路径呢?这里需要借助一个辅助数组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 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
#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             for(j=1;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[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;
}

你可能感兴趣的:(ACM,C++)