最小生成树Kruskal

今天讲述一下基于贪心思想的最小生成树Kruskal算法
先来看一下什么是生成树——
定义:如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树(SpanningTree)【摘自百度百科】
画图来理解一下
这是一个普普通通的无向图——
在这里插入图片描述
它的一个生成树
在这里插入图片描述
它的另一棵生成树
在这里插入图片描述
………………
由此我们可以发现,一个无向图有若干棵生成树
再来一句定义—— 设定点数 n, 边数m, 则生成树一共有 n - 1条边,所以,一共要删去 m - n + 1 条边
接下来,如果我们把这个无向图带上权值
在这里插入图片描述
那么,就自然引出——最小生成树中, “ 最小 ” 的概念了
那就是最终生成树的边权和最小
所以上面这个无向带权图的最小生成树就是——
在这里插入图片描述
然而怎么求一个无向图的最小生成树呢?常见有普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法
今天,我们就先讲讲相对简单的Kruskal算法

现在,先把所有边都删去——
在这里插入图片描述
自然,我们也不能不管边啊,是吧——
我们用一个数组来存储边的信息——
在这里插入图片描述

因为是无向边,所以哪里到哪里不重要,重要的是一个定点组<V1, V2>, 和它的边权
因为要求 “ 最小 ” ,我们不妨把它们按照边权 w 的大小来排个序
在这里插入图片描述
然后遍历这个存边的信息的数组,并把它加到我们的无向图里
第一次:
选取 C and D, 2
在这里插入图片描述
第二次:
选取 A and D, 3
在这里插入图片描述
第三次:
选取A and B, 5
在这里插入图片描述
诶?此时,还记得我们说过——一共要有 n - 1条边吗?这里,我们加入的边的数量达到了3 = 4 - 1,所以退出遍历存边的信息的数组,得到了我们的最小生成树

大致的思路是如此,但是还有许多小细节没有说——

并查集

假设有一条B and C 的边,但是B,C联通了,我们应该就不用这条边了
有图举例子:
在这里插入图片描述
比如此时此刻,我们存边的信息的数组是——
在这里插入图片描述
(就是多了一条C and E)
那么此时此刻,我们遍历到了 B and C, 7, 这里,但是B C 已经联通了啊,要是再加进去,那么我们的答案就一定不是 “ 最小生成树 ” 了啊,甚至都不是生成树
综上,我们在加边的时候,还要判断

if 联通(A, B) == True: 						#--- 表示A, B 连通
	Add(A, B, w)							#--- w 表示边权
	count += 1								#--- count 统计添加的边的数量

问题来了——我们如何判断定点<V1, V2> 是否联通呢?——
这里就引入了 并查集 的概念
欲知并查集为何方神圣,推荐这个Blog,(请大家期待一下后面我的并查集文章吧!)
URL:【算法与数据结构】—— 并查集

好,有了并查集,一切事情都好办了,我们再总结一下实现Kruskal的流程:

for i in range(m):	        				# 读入 m  条边
	read(from, to, w);
	edges.append(from, to, w);				# 存边的信息

sort(edges)   							    # 排序 

for i in edges:
	if join(i.from, i.to):					# 如果联通
		Add(i.from, i.to)
		count += 1	
		w += i.w							# 统计最小生成树的权值和
	if count == n - 1:						# 退出条件
		break		

有了以上伪代码,我们就开始真正实现Kruskal算法了

#include <stdio.h>
#include <algorithm>

class edge
{
public:
	edge(int ofrom = 0, int oto = 0, int ow = 0): from(ofrom), to(oto), w(ow)
	{
		
	};
	
	int from, w, to;
};

edge gra[200005];									// 按顺序存边 
int pre[5005];
int n, m, u, v, w, count = 0, ans = 0;

bool compare(const edge x, const edge y)
{
	return x.w < y.w;	
}

int find(int root)									// 并查集操作之

{
	if(pre[root] == root)
		return root;
	return pre[root] = find(pre[root]);
}

bool join(int root1, int root2)						// 并查集操作之并集
{
	int x1 = find(root1);
	int x2 = find(root2);
	
	if(x1 != x2)
	{
		pre[x1] = x2;
		return true;
	}
	
	return false;
}

int main()
{
	scanf("%d%d", &n, &m);
	
	for(int i = 1; i <= m; i++)						// 读入 m 条边
	{
		scanf("%d%d%d", &u, &v, &w);
		gra[i] = edge(u, v, w);
	}
	
	for(int i = 1; i <= n; i++)						// 初始化并查集
		pre[i] = i;
	
	std::sort(gra + 1, gra + 1 + m, compare);		// 排序
	
	for(int i = 1; i <= m; i++)
	{
		if(join(gra[i].to, gra[i].from))
		{
			ans += gra[i].w;
			count += 1;
		}
		
		if(count == n - 1)
			break;
	}
	
	if(count != n - 1)								// 无法生成最小生成树 
		puts("Can't Create!");
	else
		printf("%d\n", ans);
	
	return 0;
}

好了,讲到这里,就结束了。
若是对这些有兴趣的,请一定一定关注我,一起多多学习
我是Michael_cmr,蒟蒻一枚。如果有说错的地方,各位大神千千万万要指出来。
谢谢大家!欢迎各位大神指点!
(转载请标注出处与博主姓名)
(QQ:2437844684)
(欢迎各位大神评论)
——————都看到这里了,不点个赞是不是也觉得不好意思呀~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值