本文只谈用Floyd算法来求 图中各点的最小或第k短路径。
设opt[i][j][k]表示从i到j的路径中,经过的点的编号不超过k的最短路。
边界条件opt[i][j][0] = dis[i][j] 转移方程:
opt[i][j][k] = Min(opt[i][j][k-1] , opt[i][k][k-1] + opt[k][j][k-1]) (k > 0 , 0 <= i , j <= n)
则opt[i][j][n]即为所求值。
一般的算法:
for(k=0;k<n;++k){
for(i=0;i<n;++i){
for(j=0;j<n;++j){ //下面取其最小值
if(cost[i][j]>cost[i][k]+cost[k][j]){
cost[i][j]=cost[i][k]+cost[k][j];
}
}
}
}
改进算法 用来算最小环(下面只给出伪代码)
g[i][j]=i, j之间的边长 ;
dist:=g;
for k:=1 to n do
begin
for i:=1 to k-1 do
for j:=i+1 to k-1 do
answer:=min(answer, dist[i][j]+g[i][k]+g[k][j]);
for i:=1 to n do
for j:=1 to n do
dist[i][j]:=min(dist[i][j],dist[i][k]+dist[k][j]);
end;
最小环改进算法的证明
一个环中的最大结点为k(编号最大), 与他相连的两个点为i, j, 这个环的最短长度为g[i][k]+g[k][j]+i到j的路径中所有结点编号都小于k的最短路径长度. 根据floyd的原理, 在最外层循环做了k-1次之后, dist[i][j]则代表了i到j的路径中所有结点编号都小于k的最短路径 。
综上所述,该算法一定能找到图中最小环.
const int INF = 1000000000;
const int N = 110;
int n, m; // n:节点个数, m:边的个数
int g[N][N]; // 无向图
int dist[N][N]; // 最短路径
int r[N][N]; // r[i][j]: i到j的最短路径的第一步
int out[N], ct; // 记录最小环
int solve(int i, int j, int k){// 记录最小环
ct = 0;
while ( j != i ){
out[ct++] = j;
j = r[i][j];
}
out[ct++] = i;
out[ct++] = k;
return 0;
}
int main(void){
while( scanf("%d%d", &n, &m) != EOF ){
int i, j, k;
for ( i=0; i < n; i++ )
for ( j=0; j < n; j++ ){
g[i][j] = INF;
r[i][j] = i;
}
for ( i=0; i < m; i++ ){
int x, y, l;
scanf("%d%d%d", &x, &y, &l);
--x; --y;
if ( l < g[x][y] ) g[x][y] = g[y][x] = l;
}
memmove(dist, g, sizeof(dist));
int Min = INF; // 最小环
for ( k=0; k < n; k++ ){//Floyd
for ( i=0; i < k; i++ )// 一个环中的最大结点为k(编号最大)
if ( g[k][i] < INF )
for ( j=i+1; j < k; j++ )
if ( dist[i][j] < INF && g[k][j] < INF && Min > dist[i][j]+g[k][i]+g[k][j] ){
Min = dist[i][j]+g[k][i]+g[k][j];
solve(i, j, k); // 记录最小环
}
for ( i=0; i < n; i++ )
if ( dist[i][k] < INF )
for ( j=0; j < n; j++ )
if ( dist[k][j] < INF && dist[i][j] > dist[i][k]+dist[k][j] ){
dist[i][j] = dist[i][k]+dist[k][j];
r[i][j] = r[k][j];
}
}
if ( Min < INF ){
for ( ct--; ct >= 0; ct-- ){
printf("%d", out[ct]+1);
if ( ct ) printf(" ");
}
}
else printf("No solution.");
printf("/n");
}
return 0;
}
下面就举个例子来应用Floyd
Toj2870
The K-th City
Given a map of your country, there are N cities. The cities are labeled as 0, 1, ..., N - 1, and you live in city 0. Can you calculate out the K-th nearest city form you? If two or more cities have the same distance form you, you may assume that the city with smaller label is nearer than the city with bigger one.
Input
There are several cases. The first line of each case is two integers N and M (1 ≤ N ≤ 200, 0 ≤ M ≤ 10000), which is the number of cities in your country and the total number of roads in your country. There are three integers in each of the following M lines, A, B, C, which descript one road. A and B are the two cities that connected by that road, and C is the length of that road (1 ≤ C ≤ 2000). The roads are of both directions, and no two roads connect two same cities. There is at least one path between any two cities. At the last line of each case is a single integer K (1 ≤ K < N).
The last case is followed by a line with a single 0.
Output
Print the label of the K-th nearest city.
Sample Input
4 3 0 1 120 0 2 180 1 3 40 3 4 3 0 1 120 0 3 60 3 2 30 1 0
Sample Output
2 3
解法:(摘自别人的博客)
#include<stdio.h>
#define max 202
#define bignum 100000
int a[max][max];
void Read(int m,int n)
{
int i,j,v1,v2,weight;
for(i=0;i<m;i++)
for(j=0;j<m;j++)
if(i==j)a[i][j]=0;
else
a[i][j]=bignum;
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&v1,&v2,&weight);
if(weight<a[v1][v2])
{
a[v1][v2]=weight;a[v2][v1]=weight;
}
}
}
void Floyd(int m)
{
int i,j,k;
for(k=0;k<m;k++)
for(i=0;i<m;i++)
for(j=0;j<m;j++)
if(a[i][k]+a[k][j]<a[i][j])
a[i][j]=a[i][k]+a[k][j];
}
int main()
{
int m,n,k,i,j;
int flag;
int answer;
while(scanf("%d",&m)!=EOF)
{
if(m==0)break;
scanf("%d",&n);
Read(m,n);
scanf("%d",&k);
Floyd(m);
for(j=1;j<=k+1;j++)
{
answer=bignum-1;
for(i=0;i<m;i++)
if(answer>a[0][i])
{
answer=a[0][i];
flag=i;
}
a[0][flag]=bignum;
}
printf("%d/n",flag);
}
return 0;
}