最小生成树 Prim + Kruskal

1. 问题

在一给定的无向图 G < V , E > G<V, E> G<V,E> 中, E < u , v > E<u, v> E<u,v> 代表连接顶点 u u u与顶点 v v v 的边,而 W < u , v > W<u, v> W<u,v> 代表此边的权重。若存在 T T T E E E 的子集且为无环图,使得的 W ( T ) W(T) W(T) 最小,即需要找到一颗最小权重生成树(简称最小生成树)。

2. 解析

P r i m Prim Prim 算法:开始随机选择一个起点,将其收录进集合 S e t Set Set S t e p Step Step:遍历 S e t Set Set 中的点 u u u,找到一条最短的边权 W < u , v > W<u,v> W<u,v>,并且满足这条边 E < u , v > E<u,v> E<u,v> 中的 v v v 不属于 S e t Set Set 中,然后将 v v v 收录进 S e t Set Set 中。重复 S t e p Step Step 操作直至收录完所有的点。

在这里插入图片描述

K r u s k a l Kruskal Kruskal 算法: S t e p Step Step:从边集 E < u , v > E<u,v> E<u,v> 选择一条未被选择过的且边权最小的 W < u , v > W<u,v> W<u,v>,并且点 u u u 和点 v v v 不包含于同一个集合中,然后将点 u u u 和点 v v v 所处的两个集合合并(并查集)。重复Step操作直至找到符合条件的n-1条边。

在这里插入图片描述

3. 设计

Prim算法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e3+10;

struct node {
	int u,v,w;
};

int n,m,k;
int mp[N][N];
int dis[N];
pair<int,int>fa[N];
int sum;
vector<node>res;

/**
 * 建图
 */
void add(int u,int v,int w) {
	mp[u][v] = mp[v][u] = min(mp[u][v],w);
}

/**
 * 松弛操作是为了在稠密图的情况下降低时间复杂度
 * 如果为了找出边权最小的边而直接遍历
 * 当前集合中的点连接所有的边,总时间复杂度会是O(n*(n+m))
 * 所以我们要在寻找最小边权的同时处理dis数组
 * 总时间复杂度就会变成O(n*(n+n));
 * dis[i]数组代表当前集合中的某点到i点的最短距离
 */
void slack(int u) {
	for (int v=1;v<=n;v++) {
		int w = mp[u][v];
		if (dis[v] == -1) continue;
		if (w < dis[v]) {
			dis[v] = w;
			fa[v] = {u,w};
		}
	}
}


/**
 * 遍历集合中的点
 * 找出集合中点的边权最小的边连接的那个点加入集合
 * 如果这条边连接的点已经是集合中的点则跳过
 */
void Prim() {
	dis[1] = 0;
	for (int i=1;i<=n;i++) {
		int idx,mi = 1e9;
		for (int j=1;j<=n;j++) {
			if (dis[j] == -1) continue;
			if (dis[j] < mi) {
				idx = j;
				mi = dis[j];
			}
		}
		if (mi==1e9) {
			puts("MST is not existed");
			return;
		}
		sum += mi;
		dis[idx] = -1;
		slack(idx);
	}

	printf("MST is existed and total weight = %d\n",sum);
	puts("follwing the edges:");
	for (int i=1;i<=n;i++) {
		if (fa[i].first==i) continue;
		printf("V%d->V%d Weight = %d\n",fa[i].first,i,fa[i].second);
	}
}

void run() {
	scanf("%d %d",&n,&m);
	int u,v,w;

    /**
     * 预处理数据
     */
	for (int i=1;i<=n;i++) {
		dis[i] = 1e9;
		fa[i].first = i;
		for (int j=i;j<=n;j++) {
			mp[i][j] = mp[j][i] = 1e9;
		}
	}
	for (int i=1;i<=m;i++) {
		scanf("%d %d %d",&u,&v,&w);
		add(u,v,w);
	}
	Prim();
}

int main() {
    run();
    return 0;
}

Kruskal算法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+10;


/**
 * 存无向图的边 + 按边权升序排序
 */
struct node {
	int u,v,w;
	bool friend operator<(const node &x, const node &y) {
		return x.w < y.w;
	}
}e[N];

int n,m,k;
int fa[N];
vector<node>res;

/**
 * 并查集
 * 用于判断两个点是否存在一个集合中
 */
int get(int x) {
	return fa[x] == x ? x : fa[x] = get(fa[x]);
}

void Kruskal() {
	int sum = 0;
	int tot = 0;
    for (int i=1;i<=m;i++) {
		int u = e[i].u;
		int v = e[i].v;
		int w = e[i].w;

		/**
		 * 判断两个点是否已经处于一个集合中
		 * 如果相同,则跳过当前这条边,防止出现环
		 */
		int x = get(u);
		int y = get(v);
		if (x == y) continue;


		/**
		 * 如果两个点不在一个集合中
		 * 则将它们合并成一个集合,并选取当前这条边
		 */
		fa[x] = y;
		tot += w;
		sum++;
		res.push_back({u,v,w});
		if (sum == n-1) break;
	}
	if (sum != n-1) {
		puts("MST is not existed");
		return;
	}

	printf("MST is existed and total weight = %d\n",tot);
	puts("follwing the edges:");
	for (auto g:res) {
		printf("V%d->V%d Weight = %d\n",g.u,g.v,g.w);
	}
}

void run() {
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;i++) {
		fa[i] = i;
	}
	for (int i=1;i<=m;i++) {
		scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].w);
	}
	sort(e+1,e+m+1);
	Kruskal();
}

int main() {
    run();
    return 0;
}

4. 分析

Kruskal算法首先需要将m条边 E < u , v , w > E<u,v,w> E<u,v,w>根据w进行升序排序,然后遍历这m条边,每次遍历需要判断 u u u , v v v 是否处于一个集合中,并查集的查询复杂度为 O ( l o g N ) O(logN) O(logN) 。故总时间复杂度为 O ( E l o g E ) O(ElogE) O(ElogE),适用于稀疏图。
Prim算法需要进行n次收录操作,每次收录操作过程中还需要遍历所有的点集 V V V来找到当前点集 S e t Set Set最小的边权,即遍历整个 d i s [ i ] dis[i] dis[i] 数组找到最小值。故总时间复杂度为 O ( V 2 ) O(V^2) O(V2),适用于稠密图。

5. 源码

https://github.com/a894985555/Algorithm/tree/main/%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值