【图论】畅通道路 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。如果有什么问题,欢迎讨论。

【图论】畅通道路 prim Kruskal

时间: 2024-08-29 12:55:40

【图论】畅通道路 prim Kruskal的相关文章

hdu 1233 还是畅通工程(prim||kruskal)

这个完完全全就是模板题目,没有一点变化,就是单纯的让求最小生成树 代码:(prim) #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<vector> #include<set> #include<queue> #include<string> #inclu

数据结构之 图论---最小生成树(prim + kruskal)

图结构练习——最小生成树 Time Limit: 1000MS Memory limit: 65536K 题目描述 有n个城市,其中有些城市之间可以修建公路,修建不同的公路费用是不同的.现在我们想知道,最少花多少钱修公路可以将所有的城市连在一起,使在任意一城市出发,可以到达其他任意的城市. 输入 输入包含多组数据,格式如下. 第一行包括两个整数n m,代表城市个数和可以修建的公路个数.(n<=100) 剩下m行每行3个正整数a b c,代表城市a 和城市b之间可以修建一条公路,代价为c. 输出

最小生成树详解 prim+ kruskal代码模板

最小生成树概念: 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出.最小生成树其实是最小权重生成树的简称. prim: 概念:普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小. 实现过程: 图例 说明 不可选 可选 已选(Vn

最小生成树算法详解(prim+kruskal)

最小生成树概念: 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出.最小生成树其实是最小权重生成树的简称. prim: 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小. p

poj1861 最小生成树 prim &amp; kruskal

// poj1861 最小生成树 prim & kruskal // // 一个水题,为的只是回味一下模板,日后好有个照应不是 #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <iostream> using namespace std; const int MAX_N = 1008; const int INF =

hdu 1162 Eddy&#39;s picture 最小生成树入门题 Prim+Kruskal两种算法AC

Eddy's picture Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 7428    Accepted Submission(s): 3770 Problem Description Eddy begins to like painting pictures recently ,he is sure of himself to

还是畅通工程 -- prim算法

#include <stdio.h> #include <string.h> #define INF 100000000 int map[101][101]; int dis[101]; int vis[101]; int n; long long ans = 0; void prim() { int i,j; int min,pos; memset(vis,0,sizeof(vis)); memset(dis,INF,sizeof(INF)); vis[1] = 1; dis[1

hdu 1863 畅通工程 最小生成树模板入门题 prim+kruskal两种算法AC。

畅通工程 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 18866    Accepted Submission(s): 8012 Problem Description 省政府"畅通工程"的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可).经过调查评估,得到的统计表中列出

HDU 1233 prim kruskal最小生成树模板题

A - 还是畅通工程 Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Description 某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离.省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小.请计算最小的公路总长度. Input 测试输入包含若干测试