并查集——最小生成树算法Kruskal

连通图: 在无向图中,若任意两个顶点 V i V_i Vi V j V_j Vj都有路径相通,则称该无向图为连通图

强连通图: 在有向图中,若任意两个顶点 V i V_i Vi V j V_j Vj都有路径相通,则称该有向图为强连通图

连通网: 在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权值,权值代表着连接各个顶点的代价,称这种连通图叫做连通网

生成树: 一个连通子图,它包含连通图中全部n个顶点,有n-1条边。如果生成树中再添加一条边,则必定成环

最小生成树: 在连通网的所有生成树中,所有边的代价之和最小的生成树,称为最小生成树

在这里插入图片描述

Kruskal算法是处理边的,所以在稀疏的边比较少的连通网中,用Kruskal克鲁斯卡尔算法效率就比较高;在边比较多,点比较少的连通网中,用加点法Prim算法效率比较高

Kruskal算法:此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里,步骤如下:

  1. 把图中的所有边按权值从小到大排序,把图中的n个顶点看成独立的n颗树组成的森林
  2. 按权值从小到大选择边,所选的边连接的两个顶点 V i V_i Vi V j V_j Vj应属于两颗不同的树,则称为最小生成树中的一条边,并合并成一棵树
  3. 重复步骤2,直到所有顶点都在一棵树内或者有n-1条边为止

在这里插入图片描述

我们接下来求该联通网的最小生成树,选择权值最小的A-C边,由于A、C不在一个集合(并查集),没形成环路

在这里插入图片描述
然后选出权值为2的D-F边,此时D、F分别属于两个集合,D-F连接起来也不产生回路
在这里插入图片描述
此时B-E边的权值最小,我们选择B-E,B、E分别属于两个集合,连接起来也没有回路
在这里插入图片描述

此时C-F的边的权值最小,我们选择C-F,C、F分别属于两个集合,连接起来没有回路

在这里插入图片描述
现在B-C,C-D,A-D的权值都是5,都是当前权值最小的边。但是C-D或者A-D都属于同一个集合(并查集),一旦连接就形成回路了
所以我们选择B-C,因为B-C属于不同的集合,连接起来不会形成回路

在这里插入图片描述

我们接下来使用并查集的方式来选择边,当一条边的两个节点属于同一个集合时,我们不能选择这条边,否则会形成回路;这条边的两个节点不属于同一个集合时,我们选择这条边,此时不会形成回路

注意了,并查集方法中有一个parent数组,这个parent数组最后展现出来的并查集结构,并不是实际的最小生成树结构,只是利用并查集判断某条边的两个顶点是否在同一个集合,进而确定是否选择这条边

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int parent[1000] = { 0 };

struct Edge {
	Edge(char start, char end, int w)
		: start_(start)
		, end_(end)
		, w_(w)
	{}

	int start_;
	int end_;
	int w_;
};

// 找编号为x的节点的根节点
int find(int x) {
	if (x == parent[x]) {
		return x;
	}
	else {
		// find优化,查找根节点的过程中,把遍历过的节点的父节点都改成根节点
		parent[x] = find(parent[x]);
		return parent[x];
	}
}

int main(){
	int num_edge = 0;
	cin >> num_edge;
	vector<Edge> edges;

	char start, end;
	int w;
	// 输入数据,保存边
	for (int i = 0; i < num_edge; i++) {
		cin >> start >> end >> w;
		edges.emplace_back(start, end, w);
		// 初始化parent数组
		parent[start] = start;
		parent[end] = end;
	}
	// 边进行升序排列
	sort(edges.begin(), edges.end(), 
		[](const Edge& e1, const Edge& e2) ->bool{
		return e1.w_ < e2.w_;
		});
	// 开始合并集合,如果根节点不在同一集合,则可以合并
	for (int i = 0; i < num_edge; i++) {
		int root1 = find(edges[i].start_);
		int root2 = find(edges[i].end_);
		if (root1 != root2) {
			// 根节点不同,可以连接
			parent[root1] = root2;
			printf("%c - %c , w = %d\n", edges[i].start_, edges[i].end_, edges[i].w_);
		}
	}
	return 0;
}

/*
10
A B 6
A C 1
A D 5
B C 5
C D 5
B E 3
C E 6
C F 4
E F 6
D F 2
*/

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值