图论之最小生成树初级应用(Prim,Kruskal)

这篇博客介绍了Prim和Kruskal两种最小生成树算法的C++实现,包括其在无向图上的应用。Prim算法类似于Dijkstra,通过逐步构建最小生成树,而Kruskal算法则使用并查集优化。同时,博客提到了这些算法在解决最短网络、局域网、繁忙都市等实际问题中的应用,并提供了相关题目实例。
摘要由CSDN通过智能技术生成

最小生成树

最小生成树对应的图一般都是无向图,有向图需要用其它不常用的算法
最小生成树

P858. Prim算法求最小生成树

和dijkstra非常像
prim
集合指当前的生成树
一个点到集合的距离: 一个点到集合的所有边中,取一个距离最小的叫做这个点到集合的距离,如果一个点没有一条边到集合,那么这个点到集合的距离为正无穷

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];
int dist[N];
bool st[N]; //含义同dijkstra

int prim(){
	memset(dist, 0x3f, sizeof dist);
	
	int res = 0;
	for (int i = 0; i < n; i ++ ){
		int t = -1;
		for (int j = 1; j <= n; j++ ){
			if(!st[j] && (t == -1 || dist[t] > dist[j]))
				t=j;
		}
		
		if (i && dist[t] == INF) return INF;
		if (i) res += dist[t]; 
		//上面两行不能写后面,因为如果写后面当t有自环时dist[t]又会被更新
		
		st[t]=true;
		
		for (int j = 1; j <= n; j ++ ){
			dist[j] = min(dist[j], g[t][j]); //注意这行和dijkstra的区别 
		}
		
	}
	
	return res;
}

int main(){
	memset(g, 0x3f, sizeof g);
	scanf("%d%d", &n, &m);
	while (m -- ){
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		g[a][b] = g[b][a] = min(g[a][b], c); 
	}
	
	int t = prim();
	
	if (t == INF) puts("impossible");
	else printf("%d\n", t);
	
	return 0;
}

P859. Kruskal算法求最小生成树

比堆优化prim思路简单多了
kruskal

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N =  100010, M = 200010, INF = 0x3f3f3f3f;

int n, m;
int p[N];

struct Edge{
	int a, b, w;
}edges[M];

bool cmp(Edge a, Edge b){
	return a.w < b.w;
}

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

int kruskal()
{
	sort(edges, edges + m, cmp);
	
	for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
	
	int res = 0, cnt = 0;
	for (int i = 0; i < m; i ++ ){
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		a = find(a), b = find(b);
		if (a != b){
			p[a] = b;
			res += w;
			cnt ++ ;
		}
	}
	
	if (cnt < n-1) return INF;
	return res;
}
int main(){
	scanf("%d%d", &n, &m);
	
	for (int i = 0; i < m; i ++ ){
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		edges[i] = {a, b, w};	
	}
	
	int t = kruskal();
	
	if (t == INF) puts("impossible");
	else printf("%d\n", t);
	
	return 0;
}

正确性证明可用反证法:
证明

P1140. 最短网络

模板题

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std; 

const int N = 110;

int n;
int w[N][N];
int dist[N];
bool st[N];

int prim()
{ //同上面的模板
	memset(dist, 0x3f, sizeof dist);
	
	int res = 0;
	for (int i = 0; i < n; i ++ )
	{
		int t = -1;
		for (int j = 1; j <= n; j++ )
		{
			if(!st[j] && (t == -1 || dist[t] > dist[j]))
				t=j;
		}
		
		if (i) res += dist[t]; 	
		st[t]=true;
		
		for (int j = 1; j <= n; j ++ )
		{
			dist[j] = min(dist[j], w[t][j]); 
		}
		
	}
	
	return res;
}
 
int main()
{
	scanf("%d", &n);
	
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ )
			scanf("%d", &w[i][j]); 
	
	int t = prim();
	
	printf("%d\n", t);
	
	return 0;
}

P1141. 局域网

小呆呆

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 110, M = 210;

int p[N];
int n, m;

struct Edge{
	int a, b, w;
}edges[M];

bool cmp(Edge a, Edge b)
{
	return a.w < b.w;
}

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

int kruskal()
{
	sort(edges, edges + m, cmp);
	
	for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
	
	int res = 0;
	for (int i = 0; i < m; i ++ ){
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		a = find(a), b = find(b);
		if (a != b) p[a] = b;
		else res += w; //要在这里加
	}
	
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	
	for (int i = 0; i < m; i++ )
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		edges[i] = {a, b, w};
	}
	
	int t=kruskal();
	
	printf("%d\n", t);
	
	return 0;
}

P1142. 繁忙的都市

求最大值的最小可以想到二分,此题确实可以用二分 + 并查集 (可以看题解),这里还是采用最小生成树
小呆呆

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 310, M = 8010;

int p[N];
int n, m;

struct Edge{
	int a, b, w;
}edges[M];

bool cmp(Edge a, Edge b)
{
	return a.w < b.w;
}

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

int kruskal()
{
	sort(edges, edges + m, cmp);
	
	for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
	
	int res = 0;
	for (int i = 0; i < m; i ++ ){
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		a = find(a), b = find(b);
		if (a != b)
		{
			p[a] = b;
			res = w; //当最后一条边加入时就是答案了 
		}
	}
	
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	
	for (int i = 0; i < m; i++ )
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		edges[i] = {a, b, w};
	}
	
	int t=kruskal();
	
	printf("%d %d\n", n-1, t);
	
	return 0;
}

P1143. 联络员

kruskal拓展
分析1
思路
注意这题初始化并查集不能再写到函数中了,因为一开始读边时就要用并查集

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 2010, M = 10010;

int p[N];
int n, m;
int res;
int idx; //存所有非必选边

struct Edge{
	int a, b, w;
}edges[M];

bool cmp(Edge a, Edge b)
{
	return a.w < b.w;
}

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

int kruskal()
{
	sort(edges, edges + idx, cmp);
	
	for (int i = 0; i < idx; i ++ )
	{
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		a = find(a), b = find(b);
		if (a != b)
		{
			p[a] = b;
			res += w; 
		}
	}
	
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	
	for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
	
	for (int i = 0; i < m; i++ )
	{
		int k, a, b, w;
		scanf("%d%d%d%d", &k, &a, &b, &w);
		if (k == 1)
		{
			p[find(a)] = find(b);
			res += w;
		}
		else edges[idx++] = {a, b, w};
	}
	
	int t=kruskal();
	
	printf("%d\n", t);
	
	return 0;
}

P1144. 连接格点

方法基本通上题,先把连接好的边连上,再从剩下的边里面选,因为怕排序时间比较紧(kruskal算法时间瓶颈就在排序上),我们先建纵向的边(权值为1),再建横向的边(权值为2),这样就省去了一步排序的过程
排序与不排序的差距:
差距
二维转为一维的方式可以参考:AcWing 1131. 拯救大兵瑞恩
建边函数get_edges类似bfs,每次从一个点搜上下左右四条边,但要想办法先加纵边再加横边
一开始给每个点一个专属的编号,类似下面这样:编号

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1010, M = N * N, K = 2 * N * N; 
//M为点数,K为边数 

int n, m, idx;
int ids[N][N]; //二维坐标转一维
int p[M];

struct Edge{
	int a, b, w;
}edges[K];

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

void get_edges() //建边函数,类似bfs
{
	int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1}, dw[] = {1, 2, 1, 2};
	
	for (int z = 0; z < 2; z ++ )
		for (int i = 1; i <= n; i ++)
			for (int j = 1; j <= m; j ++ )
				for (int u = 0; u < 4; u ++ )
				{
					if (u % 2 == z) //保证先建纵边 
					{
						int x = i + dx[u], y = j + dy[u], w = dw[u];
						if(x >= 1 && x <= n && y >= 1 && y <= m)
						{
							int a = ids[i][j], b = ids[x][y];
							if(a < b) edges[idx++] = {a, b, w};	//防止加两次边
						} 
					}	
				}	
}

int kruskal()
{
    int res = 0;
	for (int i = 0; i < idx; i ++ )
	{
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		a = find(a), b = find(b);
		if (a != b)
		{
			p[a] = b;
			res += w; 
		}
	}
	
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	//二维坐标转一维,给每个点一个编号t
	for (int i = 1, t = 1; i <= n; i ++ )
		for (int j = 1; j <=m; j ++ , t ++ )
			ids[i][j] = t; 
			
	for (int i = 1; i <= n * m; i ++ ) p[i] = i; //初始化并查集
	
	int x1, y1, x2, y2;
	while (cin >> x1 >> y1 >> x2 >> y2)
	{
		int a = ids[x1][y1], b = ids[x2][y2];
		p[find(a)] = find(b);	
	} 
	
	get_edges();
	
	int t = kruskal();
	
	printf("%d\n", t);
			
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值