详解 Kruskal 最小生成树算法

 求最小生成树算一般有两种算法: PrimKruskal 。 Prim 的时间复杂度为 \(O(|V|^{2})\) ,更适合稠密图。而 Kruskal 的时间复杂度为 \(O(logV)\)\(O(logE)\) 。更适合稀疏图,下面就讲一下 Kruskal 算法的实现。

1.并查集

  Kruskal 的核心就是并查集,并查集在 Kruskal 中的作用为 判断两个点是否已经联通

1.并查集的初始化

 并查集是用一个数组储存的,数组下标代表点的序号,而数组下标对应的内容则代表这个点的父亲。在最开始每个点都是独立的,因此每个点的父亲都是自己。

初始化代码:

fa[1005];
...
for(int i = 1;i <= n;i++)
{
fa[i] = i;
}
2.并查集的查找

 并查集的查找找的是这个点的最远祖先,代码为:

int find(int x){
    if(p[x]==x)
        return x;
    else
        return find(p[x]);
}

但是这种方法效率较慢,下面是一种路径压缩的查找方式。

int find(int x)
{
    if(par[x]==x)
        return x;

        fa[x]=find(fa[x]);   
        return par[x];
}

也可以写为:

int find(int x)
{
    return fa[x]=x?x:fa[x]=find(fa[x]);
}

此时 find(x) 的返回值即为这个点的最远祖先。

3.并查集合并

 并查集的名字中有“并”和"查”字,所以并查集的核心就在于合并查找
例如,下图为并查集的初始状态。

蝠气满满

将 1 和 3合并后的状态:

蝠气满满

使用树表示为:

蝠气满满

并查集合并的代码为:

void merge(int i,int j){
    fa[find(j)] = find(i);
}

2.Kruskal算法

Kruskal 的思想为对每条边的长度进行排序,从短到长一次取边,当这条边所连的点未连接时,将两点进行合并。

图解

例如要求下面这个图的最小生成树的权值。
蝠气满满

1.对所有边的长短进行排序:

1 2 3 4 5 6
1 0 4 0 5 0 0
2 4 0 3 0 0 0
3 0 3 0 2 6 1
4 5 0 2 0 9 0
5 0 0 6 9 0 10
6 0 0 1 0 10 0

2.从短到长依次取边,当使用并查集判断两点未连接时,进行合并。
3.最后得到最小生成树如图,权值和为 16 。

蝠气满满

例题:拆地毯

代码为:

#include<bits/stdc++.h>
using namespace std;
int find(int x);
int f[100003];
struct edge
{
	int u;
	int v;
	int dis;
}ed[100000];
bool cmp(edge a,edge b)
{
	return a.dis>b.dis;
}
int Kruskal(int m,int k)
{
	int ans=0;int num = 0;
	for(int i = 1;i<=m;i++)
	{
		int fu=find(ed[i].u);
		int fv=find(ed[i].v);
		if(fu==fv) continue;
		if(fu!=fv)
		{
		f[fu]=fv;
		ans+=ed[i].dis;
		num++;
		if(num==k) break;	
		} 
	}
	return ans;
}
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
int main()
{
	int n,m,k;
	scanf("%d%d%d",&n,&m,&k);
	for(int i = 1;i<=n;i++)	f[i]=i;
	
	for(int i = 1;i<=m;i++)
	{
		int x,y,d;
		scanf("%d%d%d",&x,&y,&d);
		ed[i].u=x; ed[i].v=y; ed[i].dis=d;
	}
	sort(ed+1,ed+1+m,cmp);
	printf("%d",Kruskal(m,k));
	return 0;
} 
posted @ 2023-01-09 14:49  全角的!与半角的!  阅读(172)  评论(0编辑  收藏  举报