关于图的prim和kruskal算法
图
详情介绍请见我的博客:图
prim算法
一般来说,prim算法常用于网比较稠密的图。
对于一个点来说,找到权重最小的连接点。之后对于这两个点来说,找到这两个点中权重最小的另外一个点(即没有经过遍历的点)。之后以此类推,直到遍历完成。
可以把prim算法理解为点优先。
kruskal 算法
kauskal适用于网比较稀疏的图
先对权重由小到大排序,依次遍历权重两边的点,如果点已经被遍历过,则跳过这个权重,继续遍历。直到遍历完成。
可以把kruskal算法理解为边优先。
- 对于是否遍历过 ,我在图的博客中已经说过,可以建立一个数组,用1或0来表示是否遍历成功;
- 对于邻接链表来说,可以把权重放到链表里面;
下面我将通过POJ的题来对这两个算法进行解析
POJ2485
- prim 算法
//POJ 2485高速公路
//这里用的是邻接矩阵下的图的prime算法,即基于贪心思想的深搜遍历
#include<stdio.h>
#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是边优先。看下图:
如上所示,第一步是链接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;
}
}//这条弧入集
那么,
就到这里了!