首页 > 代码库 > 【图论】畅通道路 prim Kruskal

【图论】畅通道路 prim Kruskal

最近在整理图论的算法。并且做了一些基础的题来练习,现在做一个总结,然后准备进入下一类算法的复习。

算法这个东西,就是不要害怕去编,哪怕自己只是有一点点理解,有好多点的模糊, 找一道基础的题, 对应着有一种思路解答的,去学习代码,学习里面的思路,并且自己动手跟着敲一敲,慢慢的就会理解了。    不用想着一开始就能够很彻底的理解算法的细节上思想。 理解的时候,从它所要达到的目的去思考, 知道它是要做一个什么事情,这是第一步。后面慢慢的多去练,就会更加深刻的理解里面的思想了,包括细节部分, 也会一点一点也清晰了。

感觉最近也总是提不起劲来做事情,果然是之前的大项目让自己疲惫了么,让自己消耗了太多脑细胞了咩。

噢哟哟,  想来这种情况在未来也可能是常态的,  我也得平时留心注意找一个能够让自己放松休息的方法,   劳逸结合, 这句话越来越觉得不错了。

古语真是古语,越品越有味道。

现在连最喜欢追的动漫,看起来都打不起精神来了。   学习底层的干劲也不是很强。   不过也是正常吧,  大脑在过度的使用之后,开始疲于思考,  或者说懒于思考了。

总之,这个月, 好好休息吧。睡觉

=================================================================================================================================

扯了一堆题外话,只在抒发一下自己的心情,很多事情要说出来才好。

首先总结两个图论算法,都是最小生成树算法。

prim算法:看一张图,简单明了。


prim算法的核心思想,就是以集合为中心,向外扩散。

分为两个集合,  set1{}正在处理的最小生成树点  ,set2{}待处理的最小生成树点

每次从set2{}中寻找一个与该集合相邻接的,权值最小的点,加入set1{}, 直到set2{}中的点找完了。

下面是一个模板算法:算法是参考牙签大哥的。

模板使用说明:MAXN为定义的顶点最大可达数,n为图的大小,图以邻接表的形式存放在map中,调用prim函数后,最小需要花费的值为ret。

#define MAXN 101

int n, ret;
int map[MAXN][MAXN];

void prim()
{
	int closet[MAXN];//set1{}集合的标记,标记为1的,表示加入到set1{}集合中
	int dist[MAXN];//set2{}集合中的点到set1{}集合的最小距离,随着点的加入,要逐步更新
	int i, j;
	for ( i=0; i<n; i++)
	{
		closet[i] = 0;//将点都初始化为在set2{}集合中
		dist[i] = map[0][i];//将以0号点v1为初始点,最小距离,设置为到v1的距离
	}
	closet[0] = 1;
	int u, min;
	for ( i=1; i<n; i++)//寻找剩下的n-2个点的最小距离的边
	{
		min = 100001;
		for ( j=1; j<n; j++)//寻找一个set2{}集合的,到set1{}集合距离最小的点。
		{
			if ( ! closet[j] && dist[j] < min && dist[j] > 0)
			{
				u = j;
				min = dist[j];
			}
		}

		closet[u] = 1;//找到的点设置为set1{}集合
		ret += min;//权值对应的增加
		for ( j=1; j<n; j++)//依次更新各个点到set1{}集合的最小距离
		{
			if (! closet[j] &&  map[u][j] < dist[j] )
				dist[j] = map[u][j];
		}
	}
}
Kruskal算法:依旧是一张图,简单明了。


Kruskal算法的核心,是以边的权值为出发点,从最小的权值边开始寻找,依次寻找边所依赖的两个点在不同的联通分量的点,直到最后构成n-1条边组成的最小生成树。

再上一个算法模板,

模板使用方法:vn表示图中结点个数,dis用邻接表的形式表示一幅图,dis[i][j]为0时表示i点和j点不连通,最后所需耗费的最小值存于sum中。

#include <stdio.h>
#include <queue>
using namespace std;

#define Maxv 100+5

typedef struct
{
	int v1,v2,len;
}Node;//节点的结构体,边的两个点,和边的权值

bool operator > (Node a,Node b)//设置优先队列的排列条件
{
	if( a.len > b.len )
		return true;
	return false;
}

int dis[Maxv][Maxv];//图的邻接表
int fa[Maxv];//对应点的父亲

int Getfa(int i)//寻找点i的父亲节点,  并且更新点i的父亲节点
{
	if( fa[i] == i )   return i;
	fa[i] = Getfa(fa[i]);
	return fa[i];
}

int main()
{
	int sum;
	priority_queue< Node,vector<Node>,greater<Node> > Q;  //返回最小数

	int vn,i,j;  
	while( scanf( "%d" , &vn ) != EOF )
	{
		//输入图部分
		for( i = 1 ; i <= vn ; i++ )
			for( j = 1 ; j <= vn ; j++ )
				scanf( "%d" , &dis[i][j] );
		for( i = 1 ; i <= vn ; i++ )   fa[i] = i;	
		while( !Q.empty() )  Q.pop();//清空队列的点
		//把每条边压入堆中
		for( i = 1 ; i < vn ; i++ )
			for( j = i+1 ; j <= vn ; j++ )
				if( dis[i][j] )
				{
					Node e;
					e.v1 = i , e.v2 = j , e.len = dis[i][j];
					Q.push(e);
				}

		sum = 0;
		while( Q.size() != 0 )//从最小边权值开始出发寻找,满足条件的边
		{
			Node e;
			e = Q.top();//去除最小值
			Q.pop();
			if( Getfa(e.v1) != Getfa(e.v2) )//如果边的两个点的父亲不同,即属于不同的联通分量,就连接这两个点
			{
				sum += e.len;
				fa[Getfa(e.v2)] = Getfa(e.v1);//v2的父亲的父亲 等于 v1的父亲,将v1,v2 连接起来了
			}
		}

		printf( "%d\n" , sum );    //所需的最小值
	}
	
	return 0;
}
这里比较巧妙的是使用了Getfa这个函数,能够高效的判断两个点的联通分量,并且更新点所在的联通分量。
还使用了STL里面的一个非常方便的priority_queue。这里有它的详细使用方法,很好用的。

http://www.cnblogs.com/flyoung2008/articles/2136485.html

==================================================================================================================================

接下来,就是我练习的题目的总结,解答仅供参考。

stage1:

畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1232)

这道题目我在开始的时候考虑少了一种情况。

我在开始是这样考虑的。

设置两个集合set1(满足联通的点),  set2(待分析的点)。

对于每对输入的点,如果两个点,有一个以上的点在set1中,则不用增加边将它们联通;

如果两个点都不在set1中,则需要增加一个边将它们联通。

这个逻辑表面上看起来没有错,其实有一个错误:后面扫描边的时候,有两个点都在set1中,但是这两个点是在处理上述第二种情况的时候我自己加进去的,那么就会多出一条边。就是因为这个原因,我总是无法AC。后来多块CSDN的朋友,才得以解决。感谢CSDN的朋友!

下面上代码:

#include <stdio.h>

int city[1005];//用来记录点所在的集合,或者说联通分量

int main()
{	
	int n,m;
	int count=0;
	while(1)
	{
		scanf("%d",&n);
		if( n == 0)
		{
			break;
		}		
		scanf("%d",&m);
		int i,a,b,temp,j;
		int unionIndex = 0;
		int unionNum = 0;
		for ( i=1; i<=n; i++ ) city[i] = -1;//2、城市编号从1开始
		for ( i=0; i<m; i++ )
		{
		    scanf("%d%d",&a,&b);
		    if ( a == b ) continue;
		
		    if ( (city[a] == -1) && ( city[b] == -1 ) )
		    {
		        city[a] = unionIndex;
		        city[b] = unionIndex++;
		        unionNum++;
		    }
		    else if ( (city[a] != -1) && ( city[b] == -1 ) )
		    {
		        city[b] = city[a];
		    }
		    else if ( (city[a] == -1) && ( city[b] != -1 ) )
		    {
		        city[a] = city[b];
		    }
		    //缺少对后期连接了两个集合的判断,  可以减少独立集合数 
		    //1、缺少对  输入的两个数本身就是一个集合的判断,这时不需要减少独立集合数 
		    else if ( (city[a] != city[b]) && (city[a] != -1) && ( city[b] != -1 ) )
		    {
		    	//对所有等于a集合标志的元素,归并成b集合标志的元素 
		        temp = city[a];
		        for ( j=1; j<=n; j++ )//this range !!
		            if ( city[j] == temp ) city[j] = city[b];
		        unionNum--;
		    }
		}
		
		for ( i=1; i<=n; i++ )
		{
		    if ( city[i] == -1 ) unionNum++;
		}
		//
		//到目录为止一共有unionNum个联通集,则至少需要再添unionNum-1条路
		printf( "%d\n", unionNum-1 );		
	}	
	return 0;
}
这个方法比较笨,但是也比较有效,也比较直接。我在自己处理的时候,也会先考虑从自己解决问题的角度去设计解决方法,而不是直接就套用算法之类的。

但随着到后期,相信很多算法,会内化成为我自然而然使用的设计方法。

stage2:

还是畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1233)
这里我依旧是从自己的考虑出发,凭借脑海中模糊的prim算法思想,构造出的代码,一次就ac成功了。仅供参考。

#include <stdio.h>

int map[102][102];//city start from 1

int rea_len=0;
int city[102];

int main()
{
	int n,j,i;
	while(scanf("%d",&n) && n)
	{
		int x,y,dis;
		//init the map
		for(i=1 ; i < 102 ; i++)
			for(j=1 ; j<102 ; j++)
			{
				map[i][j] = 9999;
			}
		for(i=1 ; i<102 ;i++)
		{
			city[i]= 0;
		}
		
		rea_len=n;
		city[1] = 1;
		//input the city distance data
		for(i=0 ; i< n*(n-1)/2 ; i++)
		{
			scanf("%d%d%d",&x,&y,&dis);
			map[x][y] = dis;
			map[y][x] = dis;			
		}
		
		
		int k;
		int sum=0;
		while( --rea_len )
		{
			int min=9999;
			for(i=1 ; i<=n;  i++)
			{
				if( city[i] == 1)
				for(j=1 ; j<=n ;j++)
				{
					if(i!=j && city[j] == 0 && (map[i][j] < min || map[j][i] < min) )
					{
						min = map[i][j];
						k = j;
					}	
				}
			}
			city[k] = 1;
			sum += min;
		}
		printf("%d\n",sum);	
			
		
	}
	return  0;
}

stage3:

畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1863)
后面的解题,主要依靠Getfa函数,并且随着解题的深入,对于Getfa函数的理解也越来越深刻了。靠着Getfa函数,后面解题势如破竹。这个东西真是好用。

#include <stdio.h>
#include <queue>
using namespace std;

#define MAX  102

typedef struct
{
	int x,y,cost;
}NODE;

bool operator > (NODE a, NODE b)
{
	if( a.cost > b.cost ) return true;
	return false;
}

//记录i点的父亲的值 
int fa[MAX];

//寻找并且更新i点的最终父亲 
int getfa(int i)
{
	if( fa[i] == i) return i;
	fa[i] = getfa( fa[i] );
	return fa[i];
}

int main()
{
	int n,m,i,j,sum;
	priority_queue< NODE , vector<NODE>, greater<NODE> > Q;
	
	while( scanf("%d",&n) && n)
	{
		while( !Q.empty() ) Q.pop();
		sum=0;
		scanf("%d",&m);
		int x,y,cost;
		NODE e;
		while( n-- )
		{
			scanf("%d%d%d",&x,&y,&cost);
			e.x = x;
			e.y= y;
			e.cost = cost;	
			Q.push(e);
		}	
		for(i=1 ; i<=m ;i++) fa[i] = i;
		
		while( Q.size() )
		{
			e = Q.top();
			Q.pop();
			if( getfa(e.x) != getfa(e.y) )
			{
				sum += e.cost;
				fa[getfa(e.x)] = getfa(e.y);
			}
		}
		for(i=1 ; i<m ;i++)
		{
			if( getfa(i) != getfa(i+1) )
			{
				printf("?\n");
				break;
			}
		}
		if( i == m)
		{
			printf("%d\n",sum);
		}
	}
	
	return 0;
}

stage4:

欧拉回路(http://acm.hdu.edu.cn/showproblem.php?pid=1878)

这道基础题关键就是要分析出欧拉回路的特点:

判断一个图是否存在欧拉回路的充要条件是:1)每个点的度数为偶数,2)图是连通的

#include <stdio.h>

int fa[1003];
int count[1003];

int getfa(int i)
{
	if(fa[i] == i)  return i;
	fa[i] = getfa( fa[i] );
	return fa[i];
}

bool isOK(int n)//判断联通,以及节点度数是否为偶数
{
	int i;
	for(i=1 ;i<n ; i++)
	{
		if( getfa(i) != getfa(i+1) || count[i] %2 != 0)
			return false;		
	}
	if( count[i]%2 != 0) return false;
	return true;
}

int main()
{
	int n,m,i;
	while(  scanf("%d",&n)  && n)
	{
		scanf("%d",&m);
		int x,y;
		for(i=1 ; i<=n ; i++)
		{
			fa[i] = i;
			count[i] = 0;
		}		
		
		while(m--)
		{
			scanf("%d%d",&x,&y);
			count[x]++;
			count[y]++;
			//  将x父亲的父亲指定为 y 的父亲。从根源处将两点联通。
			// x--x1--x2  y--y1--y2  ,  x2--y2
			fa[ getfa(x)] = getfa(y);
		}
		
		if( isOK(n) )
			printf("1\n");
		else
			printf("0\n");
		
	}
	return 0;
}
stage5:

继续畅通工程(http://acm.hdu.edu.cn/showproblem.php?pid=1879)

//1.operator 操作数的使用方法
//2.priority_queue 的定义方法 

#include <stdio.h>
#include <queue>
using namespace std;

typedef struct
{
	int x,y,cost;
}NODE;

int fa[103];

int getfa(int i)
{
	if( fa[i] == i) return i;
	fa[i] = getfa( fa[i]);
	return fa[i];
}

bool operator > (NODE a, NODE b)
{
	if(a.cost > b.cost ) return true;
	return false;
}

int main()
{
	int n,i,sum;
	priority_queue< NODE, vector<NODE>, greater<NODE> > Q;
	while( scanf("%d", &n) && n)
	{
		while( !Q.empty() ) Q.pop();
		for(i=1 ;i<=n; i++)
		{
			fa[i] = i;
		}
		sum=0;
		
		int x,y,cost,isbuild;
		NODE e;
		for(i=1 ;i<= n*(n-1)/2 ; i++)
		{
			scanf("%d%d%d%d",&x ,&y, &cost, &isbuild);
			if( isbuild == 1) 
			{
				fa[ getfa(x)] = getfa(y);
				continue;
			}			
			e.x = x;
			e.y = y;
			e.cost = cost;
			Q.push(e);
		}
		//由于没有一个比较有效的实时判断图是否已经连通的函数
		//因此,将队列中所有的点都判断一遍,以做到在连通的情况下
		//计算出最小成本 
		while( Q.size() )
		{
			e = Q.top();
			Q.pop();
			if( getfa(e.x) != getfa(e.y) )
			{
				fa[ getfa(e.x)] = getfa(e.y);
				sum += e.cost;
			}
		}
		printf("%d\n",sum);
	}
	
	
	return 0;
}

感谢牙签,感谢wxspll。如果有什么问题,欢迎讨论。奋斗