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
for(int j=0; j
for(int k=0; k
问题出在我们太早的把i-k-j的距离确定下来了,假设一旦找到了i-p-j最短的距离后,i到j就相当处理完了,以后不会在改变了,一旦以后有使i到j的更短的距离时也不能再去更新了,所以结果一定是不对的。所以应当象下面一样来写程序:
for(int k=0; k
for(int i=0; i
for(int j=0; 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
2 #include
3 #include
4 using namespace std;
5 #define data 100000000
6 #define N 110
7 #define min(x,y) ((x)>(y)?(y):(x))
8 int map[N][N],dis[N][N];
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
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是直接从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 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(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;
}