11.23—11.29 最小生成树(Prim算法和Kruskal算法)

本文介绍了最小生成树的概念及其在通信网络建设中的应用,重点讲解了Prim算法和Kruskal算法的原理和实现。Prim算法采用加点法,每次选择与已连接点权值最小的未连接点;而Kruskal算法采用加边法,按边权值排序后逐步连接,使用并查集避免形成环路。这两种算法在解决最小成本连接问题时各有优势,适用于不同类型的图。
摘要由CSDN通过智能技术生成

最小生成树

最小生成树(minimum spanning tree)是由n个顶点,n-1条边,将一个连通图连接起来,且使权值最小的结构(代价最小)。

比如给N个村庄架设通信网络,该如何将这几个村庄都连接起来,成本还最小的问题。

最小生成树的三个性质:

  • 最小生成树是树,因此其边数等于顶点数减1,且树内一定不会有环。
  • 对于给定的图G,其最小生成树可以不唯一,但其边权之和一定是唯一的
  • 由于最小生成树是在无向图上生成的,因此其根结点可以实这棵树上的任意一个结点。

无论是Prim算法还是Kruskal算法,都肯定要注意满足这三个性质。

Prim算法:(普里姆算法)

时间复杂度:O(N^2)(N为顶点数)
prim算法又称加点法,用于边数较多的带权无向连通图

  • 方法:每次找与之连线权值最小的顶点,将该点加入最小生成树集合中
  • 注意:相同权值任选其中一个即可,但是不允许出现闭合回路的情况。

基本思路是:取一个顶点,选与之连线权值最小的顶点,然后再选择与该顶点连线权值最小的点,但如果都标记过了!就退回,不然会出现闭合回路的情况。
搬运一张过程图在这里插入图片描述

kruskal(克鲁斯卡尔)算法

这里主要学习Kruskal 算法。Kruskal比较适用于稀疏图,是一种贪心算法:为使生成树上边的权值和最小,则应使生成树中每一条边的权值尽可能地小。
Kruskal算法又称为加边法,用于边数较少的带权无向连通图

  • 方法:每次找最小权值边,将边连接的两个顶点加入最小生成树集合中。
  • 注意:相同权值任选其中一个即可,但是不允许出现闭合回路的情况。
    具体做法:找出森林中连接任意两棵树的所有边中,具有最小权值的边,如果将它加入生成树中不产生回路,则它就是生成树中的一条边。这里的关键就是如何判断”将它加入生成树中不产生回路”。 多棵树要连接起来,而且不得产生回路,这不就可以应用前几天学的并查集! 如果两棵树的根节点一样,连起来必定有回路。

搬运一下过程图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
要想不连成闭合回路,连接的时候判断一下两个点是否属于同一棵树就好了,这里采用并查集的算法。

然后树的合并的时候可以按秩合并 rank(X)。(其实我觉得不按秩合并当然也没问题)那什么是树的秩呢,其实可以理解为树高。给每个点一个秩(初始值为1),每次合并的时候都用秩小的指向秩大的,可以保证树高最高为log(n)
然后更新 rank ( rank[x]指的是点x秩),在一次合并后,假设是点x和点y,x的秩小,就将 pre[x] 指向 y (意思是y是x的爸爸~),然后将 rank[y] 的与 rank[x+1] 取 max
因为rank[x]为区间x的高,将它连向y之后,y的树高就会是x的树高+1,当然也可能y在另一边树高更高,所以取max
以下为各代码块的详细说明:

  • 定义边(x,y),权为w
/* 定义边(x,y),权为w */
struct node{
	int x,y;//树一条边的起点和终点;
	int w;// 边权(边的价值或大小)
}a[110];
  • cmp排序(按权值进行排序)
bool cmp(struct node a,struct node b)
{
	return a.w<b.w;
}
  • 并查集的相关函数
int pre[maxn];//存放根节点 
int rank[maxn] = {0};//存放x的秩

int Find(int x){ //找根节点 
	int r=x;
	while(r!=pre[r]){
		r=pre[r];
	}
//	int i=x,j;
//	while(pre[i]!=r)//这里因为树的深度一般不深,所以可以省去路径压缩 
//	{
//		j=pre[i];
//		pre[i]=r;
//		i=j;
//	}
		return r;
}

void join(int x, int y, int w){
    sum += w;
    if(x == y)  return ;
    if(rank[x] > rank[y])  pre[y] = x;//如果y的秩小于x的秩,x就成为y的前驱
    else{
        if(rank[x] == rank[y])  rank[y]++;
        pre[x] = y;
    }
}  //这里的join函数是按秩排序的,但其实可以直接写进去main函数emm

这个是模版,用一下c++里的sort,其他都是c语言

#include<bits/stdc++.h>
using namespace std;
struct node{
	int x,y,w;
}a[110]; //一条边的起点和终点 
int pre[110] = {0}; //存储上级是谁的那个数组 
int find(int root)
{
	int r = root;
	while(r!=pre(r)){
		r=pre[r];
	}
	return r;
} //用来找根节点的 
bool cmp(node a,node b)
{
	return a.w<b.w;
} //按照权值进行排序 
int main()
{
	int n,m;
	while(scanf("%d%d",&m,&n),m)  
	{
		int sum=0,num=0; //总成本,总连线数 
		//memset(pre,0,sizeof(pre));
		for(int i=1;i<=n;i++) pre[i]=i;//自己做自己的上级 
		for(int i=1;i<=m;i++) 
		scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);//分别输入该结点 
		sort(a+1,a+1+m,cmp); //按照边的权值进行排序 
		for(int i=1;i<=m;i++) //经过排序后,从小到大开始枚举测试边 
		{
			int f1=find(a[i].x);
			int f2=find(a[i].y);
			if(f1!=f2)//如果不是同一棵树,代表不会构成环 
			{
				sum+=a[i].w;//sum加上这条边的权值 
				pre[f1]=f2;//修这条路 
				num++; 
			}
			if(num==n-1) break; // 边的条数==点的个数-1 
		}
		if(num==n-1) printf("%d\n",sum);
		else printf("NULL\n");
	}
	return 0;
}

一道经典例题:畅通工程

#include<bits/stdc++.h>
using namespace std;
struct node{
	int x;
	int y;
	int w;
}a[150];
int pre[150]={0};
int Find(int x){
	int r=x;
	while(r!=pre[r]){
		r=pre[r]; 
	}
	return r;
}
bool cmp(node a,node b){
	return a.w<b.w;
}

int main(){
	int m,n;
	while(scanf("%d%d",&m,n),m){
		int num=0;
		int sum=0;
		for(int i=1;i<=m;i++){
			pre[i]=i;
		}
		for(int j=1;j<=n;j++){
			scanf("%d%d%d",&a[j].x,&a[j].y,&a[j].w);
		}
		sort(a+1,a+n+1,cmp);
		for(int i=1;i<=n;i++){
			int find1=Find(a[i].x);
			int find2=Find(a[i].y);
			if(find1 != find2){
				pre[find1]=find2;
				num++;
				sum+=a[i].w;
			}
			if(num == m-1) break;
		}
		if(num == m-1) printf("%d\n",sum);
		else printf("?\n");
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值