关于图的prime和kruskal算法

作者原创,转载请留言告知!!!

关于图的prim和kruskal算法

    • prim算法
    • kruskal 算法
  • 下面我将通过POJ的题来对这两个算法进行解析
      • [POJ2485](http://poj.org/problem?id=2485)
  • 何谓并查集?

详情介绍请见我的博客:图

prim算法

一般来说,prim算法常用于网比较稠密的图。
对于一个点来说,找到权重最小的连接点。之后对于这两个点来说,找到这两个点中权重最小的另外一个点(即没有经过遍历的点)。之后以此类推,直到遍历完成。
可以把prim算法理解为点优先

kruskal 算法

kauskal适用于网比较稀疏的图
先对权重由小到大排序,依次遍历权重两边的点,如果点已经被遍历过,则跳过这个权重,继续遍历。直到遍历完成。
可以把kruskal算法理解为边优先

  1. 对于是否遍历过 ,我在图的博客中已经说过,可以建立一个数组,用1或0来表示是否遍历成功;
  2. 对于邻接链表来说,可以把权重放到链表里面;

下面我将通过POJ的题来对这两个算法进行解析

POJ2485

  • prim 算法
//POJ 2485高速公路 
//这里用的是邻接矩阵下的图的prime算法,即基于贪心思想的深搜遍历     
#include
#define M 502
int main(void)
{
	long flag=-1,min=65537,arcs[M][M];
	int i,j,n,P=0,num,visited[M],PtT[M];//最后一个用空间换时间 
	scanf("%d",&n);
	for(int k=0;k<n;k++){
		flag=-1;P=0;		//注意第二组数据时的初始化 
		scanf("%d",&num);
		for(i=0;i<num;i++){
			for(j=0;j<num;j++)
				scanf("%d",&(arcs[i][j]));		//标准输入 
			visited[i]=0;						//初始化 
		}//for
		i=0;
		while(P<num-1){
			if(visited[i]==0){
				visited[i]=1;
				PtT[P++]=i;
				for(int temp=0;temp<P;temp++)	//PtT用来记录i 
					for(j=0;j<num;j++)
						if(min>arcs[PtT[temp]][j]&&arcs[PtT[temp]][j]!=0&&visited[j]==0){		//已经遍历过的所有树的子树中权重最小且没有被遍历过的 
							min=arcs[PtT[temp]][j];
							i=j;		//之前是把这个等式放到外面,就不确定j是否为0
						}
				flag<min?flag=min:flag=flag; 
				min=65537;
			}//if
		}//while
		printf("%d\n",flag);  //注意加回车 
	}//for
	return 0;
}

  • kruskal算法
//POJ 2458 高速公路
//用邻接矩阵的kruskal算法 并查集
//头文件和排序函数请自己补充
#define MAX 502
struct LINE{
	int weight;//弧的权重
	int mmp,mvp;//弧的两个节点
}node[MAX];
int coun=0,pre[MAX],sun=0;
int Find(int n) {
	while(n!=pre[n])
		n=pre[n];
	return n;
}//寻找祖宗节点
void Merge_set(LINE node) {
	int i=Find(node.mmp);
	int j=Find(node.mvp);
	if(i!=j) {
		pre[i]=j;
		coun++;	//记录路数
		sun+=node.weight;
	}
}//这条弧入集
int main() {
	int n,num;
	int temp=0;
	scanf("%d",&n);
	for(int k=0; k<n; k++) {
		scanf("%d",&num);
		for(int i=1; i<num*num/2; i++)
			pre[i]=i;//此时并查集都是独立的
		for(int i=1; i<=num; i++)
			for(int j=1; j<=i; j++) {	//倒三角输入
				scanf("%d",&node[temp].weight);
				node[temp].mmp=i;
				node[temp].mvp=j;
				temp++;
			}
		sort(k,k+num*num/2,comp);
		for(int i=0; i<num*num; i++)
			Merge_set(node[i]);
		coun=0;
		sun=0;
		printf("%d",sun);
	}
	return 0;
}

这里讲到了并查集的概念,那么

何谓并查集?

并查集是一个包含特定数值的集合。我们不能像prime算法那样用一个visit数组来标记是否遍历,因为prim是点优先而kruskal是边优先。看下图:
关于图的prime和kruskal算法_第1张图片
如上所示,第一步是链接A和B,第二步是链接D和C,如果按照此时把每个点都标记的话,很显然,在B和C没连到一块的时候4个点就已经标记完成了。
故此时需要 知道他们这些点是不是已经连到一块了。很显然,如果每次用深搜或者广搜来检查的话有很麻烦,此时并查集就应运而生。
判断是否在一个并查集的标准就是祖宗是否一样。祖宗可以理解为几个相连点之间的任意一个点,为了容易判断,就会把祖宗设为一条线中开始的那个点,譬如上图,就可以设A为祖宗。请想一想,如果每个点有相同的祖宗,不就证明了他们在同一条线上,也就是说在同一个并查集中。
刚开始时,每个点都是一个不同的并查集,因为他们互不相连。每加入一个点的时候,就要通过find函数找出它的祖宗,如果祖宗不同,就把他们的祖宗设为一个,即归到一个并查集当中去。

int Find(int n) {
	while(n!=pre[n])
		n=pre[n];
	return n;
}//寻找祖宗节点
void Merge_set(LINE node) {
	int i=Find(node.mmp);
	int j=Find(node.mvp);
	if(i!=j) {
		pre[i]=j;
		coun++;	//记录路数
		sun+=node.weight;
	}
}//这条弧入集

那么,
就到这里了!

你可能感兴趣的:(算法和数据结构)