【算法】Kruskal最小生成树算法

目录

一、最小生成树

二、Kruskal算法求最小生成树

三、代码


一、最小生成树

什么是最小生成树? 

对于一个n个节点的带权图,从中选出n-1条边(保持每个节点的联通)构成一棵树(不能带环),使得所有边的权值和最小,即为最小生成树

要点:

  • 选n-1条边
  • 要构成一颗树,则树中不能带环
  • 节点间要互相连通,即从一个节点能够到达任意一个节点
  • 边的权重和最小


二、Kruskal算法求最小生成树

Kruskal算法适用于稀疏图,因为其过程基于边的选择,如果图中的边过多可能导致效率变低

其思路如下:

  • 将图中的所有边去掉,只留下节点
  • 按照边权的顺序,从权重小的边开始将边回填到图中
  • 如果某个边回填到图中会导致带环,则丢弃
  • 当回填了n-1条边,得到最小生成树

例如:

这是我们的原图

去掉所有的边

按照权重从小到大回填边

此时发现有两条权重为4的边,选择哪条都可以,这也说明我们的最小生成树并不是唯一的,但最小权重和一定是唯一的

此时有两条权重为5的边,但如果我们把V1和V4之间的边回填的话,就带环了,这是不符合要求的,因此我们选择V2和V3之间的边

此时已经选择了n-1条边,我们的最小生成树就为


三、代码

Kruskal算法的思想很容易理解,但代码实现还是有些难度的

例如我们该如何判断图中是否带环呢?其实很简单,如果在回填某条边时,边两端的节点位于同一个集合中就说明带环。我们可以使用并查集来判断

关于并查集,可以阅读:

【算法】并查集-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/Eristic0618/article/details/138584962接下来写一道例题练练手

P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define debug(x) cout << #x << " = " << x << '\n'
#define INF 0x3f3f3f3f
using namespace std;

#define N 5050
#define M 200020

int n, m;
int f[N]; //并查集 

struct edge
{
	int a, b, w;
}e[M];

int cmp(struct edge a, struct edge b)
{
	return a.w < b.w;
}

int check(int num) //查询某个节点所在集合的根 
{
	if(f[num] != num) return f[num] = check(f[num]); 
	//压缩路径:将路径上每个节点的父亲都修改为根节点 
	return num;
}

void merge(int a, int b) //合并两个节点所在集合
{
	if(a == b) //a,b相同 
		return;
	f[a] = b; //a的父节点置为b 
} 

bool same(int a, int b) //判断两个节点是否在同一集合 
{
	if(f[a] == f[b]) return true; //a,b父节点相同,说明在同一集合 
	else return false;
}

int Kruskal()
{
	for(int i = 1; i <= n; i++) //初始化并查集 
	{
		f[i] = i;
	}
	sort(e, e+m, cmp); //按照边权升序排序 
	int cnt = 0; //统计当前选择了多少条边 
	int ans = 0; //最小生成树边权和 
	for(int i = 0;i < m; i++) //遍历所有边 
	{
		int a = e[i].a, b = e[i].b, w = e[i].w;
		a = check(a), b = check(b);
		if(same(a, b)) //a与b处于同一连通分量
			continue;
		//a与b不处于同一连通分量
		ans += w; //选择该条边
		merge(a, b); //将a,b所在连通分量合并 
		cnt++;
		if(cnt == n-1) //已选择n-1条边 
			break;
	} 
	if(cnt < n-1) //边数不足
		return -1;
	return ans; 
}

void solve()
{
	cin >> n >> m;
	for(int i = 0;i < m; i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		e[i] = {x, y, z}; //存边 
	}
	
	int ans = Kruskal(); //Kruskal算法
	if(ans == -1) //不连通 
		cout << "orz" << endl;
	else
		cout << ans << endl;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t--)
        solve();
    return 0;
}

完.

Kruskal算法是一种用于解图中最小生成树的经典算法,它基于贪心策略,通过逐步添加边来构建一棵连通而权值之和最小的树。在C++中实现Kruskal算法的一般步骤如下: 1. 将所有边按照权重从小到大排序。 2. 初始化一个空的并查集数据结构,用于跟踪各个顶点所属的集合。 3. 遍历排序后的边,对于每条边,如果它的两个端点属于不同的集合(即它们尚未连接),就将这条边加入生成树,并合并这两个集合。 4. 重复步骤3,直到所有的顶点都连接起来,形成一棵树。因为每次选择的边都是当前未连接的权值最小的,所以最后得到的就是最小生成树。 以下是一个简单的Kruskal算法C++伪代码示例: ```cpp #include <iostream> #include <vector> #include <set> using namespace std; struct Edge { int src, dest, weight; }; class disjoint_set { public: //... 实现并查集操作 (find和union等) private: vector<int> parent; // parent[i]表示i的父亲节点 }; int kruskal(vector<Edge>& edges) { sort(edges.begin(), edges.end(), [](Edge a, Edge b) { return a.weight < b.weight; }); disjoint_set ds; for (const auto& e : edges) { if (!ds.find(e.src) || !ds.find(e.dest)) { ds.union_set(e.src, e.dest); // 把边添加到最小生成树中 } } return ds.count(); // 返回生成树的边数,即最小生成树的大小 } int main() { vector<Edge> edges = {{1, 2, 7}, {1, 3, 9}, {2, 3, 10}, ...}; // 假设这是边的列表 cout << "Minimum spanning tree size: " << kruskal(edges) << endl; return 0; } ```
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值