Floyd算法求无向图最小环

原理可看菊苣博文:http://www.cnblogs.com/khan724/p/4383686.html

自己代码中解释一些小细节。该算法适用于无向图,而有向图的最小环,实际上就是初始化所有点为inf(包括graph[i][i]),然后跑一个普通Floyd即可,寻找最小的graph[i][i]就是最小环。


代码(以POJ-1734为例):

#include 
using namespace std;
const int _inf = 0x7fffffff;
const int inf = _inf/3;	//程序可能出现3个inf相加
const int maxn = 105;  
int graph[maxn][maxn], pre[maxn][maxn], dis[maxn][maxn];
int cnt, path[maxn], sum;
int n, m;  
void init()  
{ 
    for(int i = 1; i <= n; ++i) 
    { 
    	for(int j = 1; j <= n; ++j)
    	{
    		pre[i][j] = i;
    		graph[i][j] = dis[i][j] = inf;
    	}
    	graph[i][i] = dis[i][i] = 0;
    } 
}
void Folyd()  
{  
    int mins = inf;
    for(int k = 1; k <= n; ++k)
    {
    	for(int i = 1; i < k; ++i)
    	for(int j = i+1; j < k; ++j)
    	{
    		//多一个graph数组的作用在于此,在松弛的过程中,会破坏掉两点之间是否真的存在边的表示,所有需要多开一个graph
    		int tmp = dis[i][j] + graph[k][i] + graph[j][k];	
    		//正确写法应该是我这种写法即该环为j,k,i...j,菊苣的写法跟原理不对应,但不会出错,因为是无向图嘛。
    		if(mins > tmp)
    		{
    			mins = tmp;
    			cnt = 0; sum = 1;
    			int t = i;
    			while(t != j)
    			{
					path[cnt++] = t;
					t = pre[j][t];
				}
				path[cnt++] = j;
				path[cnt++] = k;
    		}
    		else if(tmp == mins) ++sum;	//求不同的最小环的个数,i,j相同时可根据k区分,相同k可根据i,j区分,所以不会重。
    	}
    	for(int i = 1; i <= n; ++i)
    	for(int j = 1; j <= n; ++j)
    	{
    		if(dis[i][j] > dis[i][k]+dis[k][j])
    		{
    			dis[i][j] = dis[i][k]+dis[k][j];
    			pre[i][j] = pre[k][j];
    		}
    	}
    }
    if(mins == inf) puts("No solution.");
    else
    {
    	for(int i = cnt-1; i > 0; --i) printf("%d ", path[i]);
    	printf("%d\n", path[0]);
    }
}
int main()  
{  
    int u, v, w;  
    while(~scanf("%d %d", &n, &m))
    {
	    init();  
	    for(int i = 1; i <= m; ++i)  
	    {  
	        scanf("%d %d %d", &u, &v, &w); 
	        if(w < graph[u][v])
	        {
		        graph[u][v] = graph[v][u] = w;
		        dis[u][v] = dis[v][u] = w; 
		    }
	    }  
	    Folyd(); 
	}
    return 0;  
}

上面的寻路方法是通过pre数组来寻找的,还可以通过nex数组进行寻找,两种寻路方法详情:Floyd 算法求多源最短路径-打印最短路径。

另外解释一下网上经常出现的dfs寻路径的原理,通过一个find数组,find[i][j]表示i到j的最短路最终是通过哪个点松弛得到的。通过find[i][j]的值t再进行find[i][t]和find[t][j]的寻找直到t = 0时,便完成了寻找path上所有的点,最后再加上j和i之间的k点即可。


代码:

#include 
using namespace std;
const int _inf = 0x7fffffff;
const int inf = _inf/3;
const int maxn = 105;  
int graph[maxn][maxn], find[maxn][maxn], dis[maxn][maxn];
int cnt, path[maxn], sum;
int n, m;  
void init()  
{ 
    for(int i = 1; i <= n; ++i) 
    { 
    	for(int j = 1; j <= n; ++j)
    	{
    		find[i][j] = 0;
    		graph[i][j] = dis[i][j] = inf;
    	}
    	graph[i][i] = dis[i][i] = 0;
    } 
}
void dfs(int i, int j)
{
	int k = find[i][j];
	if(k == 0)
	{
		path[cnt++] = j;
		return;
	}
	dfs(i, k);
	dfs(k, j);
}
void Folyd()  
{  
    int mins = inf;
    for(int k = 1; k <= n; ++k)
    {
    	for(int i = 1; i < k; ++i)
    	for(int j = i+1; j < k; ++j)
    	{
    		int tmp = dis[i][j] + graph[k][i] + graph[j][k];
    		if(mins > tmp)
    		{
    			mins = tmp;
    			cnt = 0; sum = 1;
    			path[cnt++] = i;
				dfs(i, j);
				path[cnt++] = k;
    		}
    		else if(tmp == mins) ++sum;
    	}
    	for(int i = 1; i <= n; ++i)
    	for(int j = 1; j <= n; ++j)
    	{
    		if(dis[i][j] > dis[i][k]+dis[k][j])
    		{
    			dis[i][j] = dis[i][k]+dis[k][j];
    			find[i][j] = k;
    		}
    	}
    }
    if(mins == inf) puts("No solution.");
    else
    {
    	for(int i = cnt-1; i > 0; --i) printf("%d ", path[i]);
    	printf("%d\n", path[0]);
    }
}
int main()  
{  
    int u, v, w;  
    while(~scanf("%d %d", &n, &m))
    {
	    init();  
	    for(int i = 1; i <= m; ++i)  
	    {  
	        scanf("%d %d %d", &u, &v, &w); 
	        if(w < graph[u][v])
	        {
		        graph[u][v] = graph[v][u] = w;
		        dis[u][v] = dis[v][u] = w; 
		    }
	    }  
	    Folyd(); 
	}
    return 0;  
}

晚安_

你可能感兴趣的:(优秀算法总结,连通图)