最小生成树详解(破圈法,kruskal,prim)

1 篇文章 0 订阅
1 篇文章 0 订阅

前言

好的编程是要爬上抽象的阶梯才能到达最通用的解决方案

        如这句话所言,最小生成树其实是一个相对比较抽象的概念,它是针对于图的概念,因为树是一种特殊的图,下面将详细讲解最小生成树的概念及三种求最小生成树的方法,思路都十分简单。

目录

前言

目录

正文

---最小生成树的概念

---破圈法

---kruskal(克鲁斯卡尔)

---prim(普里姆)

例题

T1.最小生成树


正文

---最小生成树的概念

        首先,要了解最小生成树,我们知道什么是图和树,如上文所说的,树是一种特殊的图,所以,此处我们先来看图的概念。

        先来看严格定义:

        图是由顶点的有穷非空集合V ( G ) 和顶点之间边的集合E ( G ) 组成,通常表示为: G = ( V , E ) ,其中,G表示个图,V是图G中顶点的集合,E是图G中边的集合。若V = { v 1 , v 2 , . . . , v n } ,则用∣ V ∣表示图G 中顶点的个数,也称图G的阶,E = { ( u , v ) ∣ u ∈ V , v ∈ V } ,用∣ E ∣表示图G 中边的条数。

        看起来是不是很复杂,其实如果简单的来说,就是在一堆顶点之间连边,根据图的种类的不同,图也有很多不同的形式,一般来说,根据边的不同来分类有如下几类(可以结合图片理解):(但要特别注意,图不能是空的)

1.有向图(即边有方向意义的图)

2.无向图(即边无方向意义的图,也可以理解为双向)

3.带权图(即边有权值的图)

4.连通图(即所有点相连的图)

5.强连通图(本身是有向图且是连通图)

        而在最小生成树中,最重要的其实是第三种,带权图,但是在了解最小生成树之前,我们要先了解什么是树。

        定义很简单:

        如果一个无向连通图不包含回路(连通图中不存在环),那么就是一个树。

        显然,根据这句话,我们可以推出树有很多性质:

1.n个顶点的树有n-1条边

2.树中不存在环

3.树的根节点没有前驱,其他结点都只有1个直接前驱。

4.树是连通的

 

         最后,我们就要来介绍最小生成树了,对于一个连通图来说,由于其中可能存在环,所以其不一定是一棵树,而最小生成树,就是对一个带权无向连通图,将其内部的部分边删除,使其变成一棵各边权值之和最小的树。

---破圈法

算法为王,数学先行。

        对于人们来说,目前破圈法是最快且最简单的求最小生成树的方法,并且更偏向一种数学的方法,其思想十分简单,在图中找到所有的环,并且对一个环,删去其中权值最大的一条边,使得其不是一个环,这样得出来的生成树一定是最小的,如图:

        但是,破圈法很难作为一个算法在代码中被实现,原因主要是因为其过程中的删除操作,不管是在什么数据结构中,删除操作都是很难实现的,他会涉及到很多其它数据的变动,因此,这种方法更适合在目测时使用。

---kruskal(克鲁斯卡尔)

        kruskal是在算法竞赛中最常见且最实用的方法,其本质是一种贪心的思想,将每条边存下来后,按权值进行排序,然后由小到大遍历,如果将当前这条边加入图中后不会形成环就加入,否则就跳过,这样,形成的生成树也一定是最小的,但是因为其过程更偏向代数,所以此处就不放题解了。

        那么如何进行判环呢?将时间与适应性综合考虑,并查集是此处最好的判环方式,详见:

数据结构---并查集解析-CSDN博客

模板:

void kruskal(){
	int i=0,ans=0,num=0;//此处数组已经排好序
	for(int i=1;i<=cnt;i++){
		if(Union(a[i].x,a[i].y)){//并查集合并并判断是否为环
			num++;//可以
			ans+=a[i].z;//权值累加
		}
		if(num==n-1){
			break;//不行
		}
	}
	cout<<ans; 
}

---prim(普里姆)

        这个算法虽然并不是非常实用,但是其思想非常重要,基本与求最短路的算法dijskra相同,用蓝点表示已经走过的点,白点表示没有走过的点,每循环一次,将一个白点变成蓝点,而这个白点应是目前所有与蓝点直连的白点中连边边权最小的一个,当循环结束时,就能确保得到的生成树一定是最小的,如图:

模板:

void prim(){
	int ans=0;
	for(int i=1;i<=n;i++){
		int t=-1;
		for(int j=1;j<=n;j++){
			if(vis[j]==0&&(t==-1||dis[t]>dis[j])){//没有走过且是最优选
				t=j;
			}
		}
		if(dis[t]==INT_MAX){
			cout<<"orz";
			return;
		}
		vis[t]=1;
		ans+=dis[t];
		for(int j=head[t];j!=-1;j=ne[j]){
			int x=ver[j];
			if(vis[x]==0&&dis[x]>=w[j]){
				dis[x]=w[j];
			}
		}
	}
	cout<<ans;
}

(长度对比很明显....)

例题

T1.最小生成树

题面描述:

        给定一个图边的所有信息,求这个图的最小生成树的权值之和。

思路:

        模板题,直接按照上面的模板写即可。

代码:

kruskal

#include<bits/stdc++.h>
using namespace std;
int pre[5005],cnt;
struct node{
	int x;
	int y;
	int z;
}a[200005];
bool cmp(node a,node b){
	return a.z<b.z;
}
int Find(int x){
	if(pre[x]==x) return x;
	return pre[x]=Find(pre[x]);
}
bool Union(int x,int y){
	int xx=Find(x);
	int yy=Find(y);
	if(xx==yy){
		return 0;
	}
	pre[xx]=yy;
	return 1;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i].x>>a[i].y>>a[i].z;
	}
	sort(a+1,a+1+m,cmp);
	for(int i=1;i<=n;i++) pre[i]=i;
	int i=0,ans=0;
	for(int i=1;i<=m;i++){
		//cout<<a[i].z<<" ";
		if(Union(a[i].x,a[i].y)){
			cnt++;
			ans+=a[i].z;
		}
		if(cnt==n-1){
			break;
		}
	}
	if(cnt<n-1){
		cout<<"orz";
		return 0; 
	} 
	cout<<ans;
	return 0;
} 

prim 

#include<bits/stdc++.h>
using namespace std;
int n,m;
int pre[5005],cnt;
int vis[5005],dis[5005],head[400005],ver[400005],ne[400005],tot=-1,w[400005];
priority_queue<int,vector<int>,greater<int> > q;
void add(int x,int y,int z){
	tot++;
	ver[tot]=y;
	w[tot]=z;
	ne[tot]=head[x];
	head[x]=tot;
}
void prim(){
	int ans=0;
	for(int i=1;i<=n;i++){
		int t=-1;
		for(int j=1;j<=n;j++){
			if(vis[j]==0&&(t==-1||dis[t]>dis[j])){
				t=j;
			}
		}
		if(dis[t]==INT_MAX){
			cout<<"orz";
			return;
		}
		vis[t]=1;
		ans+=dis[t];
		for(int j=head[t];j!=-1;j=ne[j]){
			int x=ver[j];
			if(vis[x]==0&&dis[x]>=w[j]){
				dis[x]=w[j];
			}
		}
	}
	cout<<ans;
}
int main(){
	memset(head,-1,sizeof head);
	cin>>n>>m;
	for(int i=1;i<=n;i++) dis[i]=INT_MAX;
	dis[1]=0;
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
		add(y,x,z);
	}
	prim();
	return 0;
} 

  • 34
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C破圈法是一种用于求解最小生成树算法,它基于贪心思想,每次选择一条边,并将其加入生成树中,如果这条边不会形成环,则继续加入下一条边,直到生成树中有n-1条边为止。 具体实现步骤如下: 1.将边按照权值从小到大排序。 2.从最小的边开始,依次将边加入生成树中。 3.如果新加入的边不会使生成树形成环,则继续加入下一条边。 4.如果新加入的边会使生成树形成环,则舍弃这条边。 5.重复3、4步骤,直到生成树中有n-1条边为止。 下面是C语言实现代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 #define INF 0x3f3f3f3f int n, edgeNum, edgeCnt; int u[MAX], v[MAX], w[MAX]; //边的起点,终点,权值 int father[MAX]; //并查集数组 //并查集查找祖先节点 int find(int x) { if (x != father[x]) { father[x] = find(father[x]); } return father[x]; } //并查集合并两个节点 void merge(int x, int y) { int tx = find(x); int ty = find(y); if (tx != ty) { father[tx] = ty; } } //边的比较函数,用于排序 int cmp(const void *a, const void *b) { return (*(int *)a) - (*(int *)b); } int main() { scanf("%d%d", &n, &edgeNum); //初始化并查集 for (int i = 1; i <= n; i++) { father[i] = i; } //读入边 for (int i = 1; i <= edgeNum; i++) { scanf("%d%d%d", &u[i], &v[i], &w[i]); } //按照权值从小到大排序 qsort(w + 1, edgeNum, sizeof(int), cmp); //依次加入边 for (int i = 1; i <= edgeNum; i++) { if (find(u[i]) != find(v[i])) { merge(u[i], v[i]); edgeCnt++; printf("加入第%d条边,%d -> %d,权值为%d\n", edgeCnt, u[i], v[i], w[i]); if (edgeCnt == n - 1) { break; } } } return 0; } ``` 这段代码中使用了并查集来判断新加入的边是否会形成环,避免了使用DFS或BFS等方法来判断环,使算法更加高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值