最小生成树-Prim算法和Kruskal算法

Prim算法

1.概览

普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。

2.算法简单描述

1).输入:一个加权连通图,其中顶点集合为V,边集合为E

2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;

3).重复下列操作,直到Vnew = V

a.在集合E中选取权值最小的边,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);

b.v加入集合Vnew中,将边加入集合Enew中;

4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。

3.简单证明prim算法

反证法:假设prim生成的不是最小生成树

1).prim生成的树为G0

2).假设存在Gmin使得cost(Gmin)则在Gmin中存在不属于G0

3).加入G0中可得一个环,且不是该环的最短边(这是因为∈Gmin)

4).这与prim每次生成最短边矛盾

5).故假设不成立,命题得证.

 

Kruskal算法

1.概览

Kruskal算法是一种用来寻找最小生成树的算法。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。

2.算法简单描述

1).Graph中有v个顶点,e个边

2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边

3).将原图Graph中所有e个边按权值从小到大排序

4).循环:从权值最小的边开始遍历每条边直至图Graph中所有的节点都在同一个连通分量中

                if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中

                                 添加这条边到图Graphnew中 

3.简单证明Kruskal算法

对图的顶点数n做归纳,证明Kruskal算法对任意n阶图适用。

归纳基础:

n=1,显然能够找到最小生成树。

归纳过程:

假设Kruskal算法对n≤k阶图适用,那么,在k+1阶图G中,我们把最短边的两个端点ab做一个合并操作,即把uv合为一个点v',把原来接在uv的边都接到v'上去,这样就能够得到一个k阶图G'(u,v的合并是k+1少一条边)G'最小生成树T'可以用Kruskal算法得到。

我们证明T'+{}G的最小生成树。

用反证法,如果T'+{}不是最小生成树,最小生成树是T,即W(T)})。显然T应该包含,否则,可以用加入到T中,形成一个环,删除环上原有的任意一条边,形成一棵更小权值的生成树。而T-{},是G'的生成树。所以W(T-{})<=W(T'),也就是W(T)<=W(T')+W()=W(T'+{}),产生了矛盾。于是假设不成立,T'+{}G的最小生成树,Kruskal算法对k+1阶图也适用。

由数学归纳法,Kruskal算法得证。

注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。

例题

题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1875

题目描述

 Problem Description

相信大家都听说一个百岛湖的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现。现在政府决定大力发展百岛湖,发展首先要解决的问题当然是交通问题,政府决定实现百岛湖的全畅通!经过考察小组RPRush对百岛湖的情况充分了解后,决定在符合条件的小岛间建上桥,所谓符合条件,就是2个小岛之间的距离不能小于10米,也不能大于1000米。当然,为了节省资金,只要求实现任意2个小岛之间有路通即可。其中桥的价格为 100/米。

 

Input

输入包括多组数据。输入首先包括一个整数T(T <= 200),代表有T组数据。
每组数据首先是一个整数C(C <= 100),代表小岛的个数,接下来是C组坐标,代表每个小岛的坐标,这些坐标都是 0 <= x, y <= 1000的整数。

 

Output

每组输入数据输出一行,代表建桥的最小花费,结果保留一位小数。如果无法实现工程以达到全部畅通,输出”oh!”.

 

Sample Input

2

2

10 10

20 20

3

1 1

2 2

1000 1000

 

Sample Output

1414.2

oh!

 

题目大意

经过对百岛湖的情况充分了解后,政府决定在符合条件(所谓符合条件就是2个小岛之间的距离不能小于10米,也不能大于1000)的小岛间建上桥以实现百岛湖的全畅通!当然,为了节省资金,只要求实现任意2个小岛之间有路通即可。其中桥的价格为 100/米。

解题思路

在各个小岛间建立桥,而且要节省资金(无非就是让桥尽可能的短。当然,不能比两岛之间的距离短),这不就是最小生成树吗?加权连通图里搜索最小生成树。

代码如下:

Prim算法

#include 
#include 
#include 
#define INF 99999999
double map[110][110],dis[110];
int vis[110];
int n;
 
struct crood//建立坐标 
{
int x;
int y;
}a[110];
 
void prime()//map初始化,设为无限大 
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
map[i][j]=INF;
}
 
double getval(crood m,crood n)//两坐标之间的距离 
{
return sqrt(pow(m.x-n.x,2)+pow(m.y-n.y,2));
}
 
double prim()//普里姆算法
{
double min,sum;
int i,j,k;
memset(vis,0,sizeof(vis));//标记是否已连接 
for(i=1;i<=n;i++)
dis[i]=map[1][i];
vis[1]=1;
sum=0;
for(i=1;i=10)//选取权值最小的边
{
min=dis[j];
k=j;//标记权值最小的边 
}
}
if(min==INF)
return 0;
sum+=min;
for(j=1;j<=n;j++) 
if(!vis[j]&&dis[j]>map[k][j])
dis[j]=map[k][j];
vis[k]=1;
}
return sum;
}
 
int main()
{
int m;
int t,i,j,k,x,y;
int flag;
double ans,value;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
prime();//初始化 
for(i=1;i<=n;i++)
scanf("%d%d",&a[i].x,&a[i].y);
m=n*(n-1)/2;
for(j=1;j<=n;j++)
for(k=j+1;k<=n;k++)
{
value=getval(a[j],a[k]);
if(value>=10&&value<=1000)
map[j][k]=map[k][j]=value;
}
ans=prim();
if(ans)
printf("%.1lf\n",ans*100);
else
puts("oh!");
}
return 0;
}

 

Kruskal算法

#include 
#include 
#include 
#include 
using namespace std;
int n;
int per[110];
struct coord//建立坐标 
{
int x;
int y;
}a[110];
 
void prime()//初始化各为各的根节点 
{
for(int i=0;i<=n;i++)
per[i]=i;
}
 
struct node//建立权值 
{
int start;
int end;
double value;
}b[10010];
 
int find(int x)//寻找根节点 
{
if(per[x]==x)
return x;
int r=x,j;
while(per[r]!=r)
{
j=per[r];
r=j;
}
return r;
}
/*
int find(int x)
{
if(per[x]!=x)
return find(per[x]);
return x;
}
*/
int cmp(node x,node y)//根据权值从小到大排序 
{
return x.value=10&&b[i].value<=1000)
{
if(join(b[i].start,b[i].end))
{
sum+=b[i].value;
num++;
}
}
if(num==n) 
printf("%.1lf\n",sum*100);
else
puts("oh!");
}
return 0;
}

 

例题

题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1102

题目描述

 Problem Description

There are N villages, which are numbered from 1 to N, and you should build some roads such that every two villages can connect to each other. We say two village A and B are connected, if and only if there is a road between A and B, or there exists a village C such that there is a road between A and C, and C and B are connected. 

We know that there are already some roads between some villages and your job is the build some roads such that all the villages are connect and the length of all the roads built is minimum.

 

Input

The first line is an integer N (3 <= N <= 100), which is the number of villages. Then come N lines, the i-th of which contains N integers, and the j-th of these N integers is the distance (the distance should be an integer within [1, 1000]) between village i and village j.

Then there is an integer Q (0 <= Q <= N * (N + 1) / 2). Then come Q lines, each line contains two integers a and b (1 <= a < b <= N), which means the road between village a and village b has been built.

 

Output

You should output a line contains an integer, which is the length of all the roads to be built such that all the villages are connected, and this value is minimum. 

 

Sample Input

3

0 990 692

990 0 179

692 179 0

1

1 2

 

Sample Output

179

 

题目大意

某地有N个村庄,编号为1~N,需要在村庄之间建立一些道路,使得每两个村庄都有道路相连接(12之间有道路相连接,23之间有道路相连接,那么就可以认为13之间也有道路相连接),问把这些村庄连接起来最少需要多长道路。

解题思路

赤裸裸的最小生成树问题,将N个村庄看成结点,村庄与村庄之间的道路长度看作权值,求带有权值的最小生成树。

代码如下:

Prim算法

#include 
#include 
#include 
#define INF 99999999
int map[110][110],dis[110];
int vis[110];
int n;
 
void prime()//map初始化,设为无限大 
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
map[i][j]=INF;
}
 
int prim()//普里姆算法
{
int min,sum;
int i,j,k;
memset(vis,0,sizeof(vis));//标记是否已连接 
for(i=1;i<=n;i++)
dis[i]=map[1][i];
vis[1]=1;
sum=0;
for(i=1;imap[k][j])
dis[j]=map[k][j];
vis[k]=1;
}
return sum;
}
 
int main()
{
int m,i,j;
int x,y;
int ans,value;
while(~scanf("%d",&n))
{
prime();//初始化 
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
scanf("%d",&value);
map[i][j]=value;
}
scanf("%d",&m);
for(i=0;i

 

Kruskal算法

#include 
#include 
#include 
#include 
using namespace std;
int t;
int per[110];
 
void prime()//初始化各为各的根节点 
{
for(int i=0;i<=t;i++)
per[i]=i;
}
 
struct node//转化并建立权值 
{
int start;
int end;
int value;
}map[10010];
 
int find(int x)//寻找根节点 
{
if(per[x]==x)
return x;
int r=x,j;
while(per[r]!=r)
{
j=per[r];
r=j;
}
return r;
}
 
int cmp(node x,node y)//根据权值从小到大排序 
{
return x.value

 

例题

题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1301

题目描述

 Problem Description


The Head Elder of the tropical island of Lagrishan has a problem. A burst of foreign aid money was spent on extra roads between villages some years ago. But the jungle overtakes roads relentlessly, so the large road network is too expensive to maintain. The Council of Elders must choose to stop maintaining some roads. The map above on the left shows all the roads in use now and the cost in aacms per month to maintain them. Of course there needs to be some way to get between all the villages on maintained roads, even if the route is not as short as before. The Chief Elder would like to tell the Council of Elders what would be the smallest amount they could spend in aacms per month to maintain roads that would connect all the villages. The villages are labeled A through I in the maps above. The map on the right shows the roads that could be maintained most cheaply, for 216 aacms per month. Your task is to write a program that will solve such problems. 

The input consists of one to 100 data sets, followed by a final line containing only 0. Each data set starts with a line containing only a number n, which is the number of villages, 1 < n < 27, and the villages are labeled with the first n letters of the alphabet, capitalized. Each data set is completed with n-1 lines that start with village labels in alphabetical order. There is no line for the last village. Each line for a village starts with the village label followed by a number, k, of roads from this village to villages with labels later in the alphabet. If k is greater than 0, the line continues with data for each of the k roads. The data for each road is the village label for the other end of the road followed by the monthly maintenance cost in aacms for the road. Maintenance costs will be positive integers less than 100. All data fields in the row are separated by single blanks. The road network will always allow travel between all the villages. The network will never have more than 75 roads. No village will have more than 15 roads going to other villages (before or after in the alphabet). In the sample input below, the first data set goes with the map above. 

The output is one integer per line for each data set: the minimum cost in aacms per month to maintain a road system that connect all the villages. Caution: A brute force solution that examines every possible set of roads will not finish within the one minute time limit. 

 

Sample Input

9

A 2 B 12 I 25

B 3 C 10 H 40 I 8

C 2 D 18 G 55

D 1 E 44

E 2 F 60 G 38

F 0

G 1 H 35

H 1 I 35

3

A 2 B 10 C 40

B 1 C 20

0

 

Sample Output

216

30

 

题目大意

热带岛屿有一个难题。几年前用外国援助资金在村庄之间建造了一些道路。但丛林破坏道路无情,所以大道路网络维护成本太高。长老理事会必须选择停止维护一些道路。左边上面的地图显示了所有使用的道路现在每月维护成本。当然需要有办法让所有的村庄之间维护道路,即使不是和以前一样短的路线。首席长老想告诉议会的长老只需要维护哪些道路就可以连通所有村庄并且可以花最少的每月维护费用。

解题思路

首先需要将所有道路输入,在选择最短的道路进行维护,已达到题目的要求。这也是一道赤裸裸的最小生成树问题,只需要套用模板,再稍加修改就可以了。

代码如下:

Prim算法

#include 
#include 
#include 
#define INF 99999999
int map[110][110],dis[110];
int vis[110];
int n;

void prime()//map初始化,设为无限大 
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			map[i][j]=INF;
}

int prim()//普里姆算法
{
	int min,sum;
	int i,j,k;
	memset(vis,0,sizeof(vis));//标记是否已连接 
	for(i=1;i<=n;i++)
		dis[i]=map[1][i];
	vis[1]=1;
	sum=0;
	for(i=1;imap[k][j])
				dis[j]=map[k][j];
		vis[k]=1;
	}
	return sum;
}

int main()
{
	int m,i,j;
	int x,y,value;
	int ans;
	char c0,c1;
	while(scanf("%d",&n),n)
	{
		prime();//初始化 
		for(i=1;i

Kruskal算法:  

#include 
#include 
#include 
#include 
using namespace std;
int t;
int per[30];


void prime()//初始化各为各的根节点 
{
	for(int i=0;i<=t;i++)
		per[i]=i;
}


struct node//建立权值 
{
	int start;
	int end;
	int value;
}map[1010];


int find(int x)//寻找根节点 
{
	if(per[x]==x)
		return x;
	int r=x,j;
	while(per[r]!=r)
	{
		j=per[r];
		r=j;
	}
	return r;
}


int cmp(node x,node y)//根据权值从小到大排序 
{
	return x.value



你可能感兴趣的:(基本算法总结)