C++图的两种构建算法流食般投喂-竞赛编

此篇资料来自比特官网,以及小编手搓~
图的构建算法:

1、prim算法

2、kruskal算法(克鲁斯卡尔算法)

1、prim算法

prim算法的核心是不断加点,一直加,直到所有点都加完,所以只有在每次加点的过程中,通过最短距离来判断,端点之间是不是连通的,如果最短距离是无穷大,则不连通;是正常值,则连通。

prim算法构造最小生成树的基本思想:

1、从全部顶点中任选一个来开始构造最小生成树,这一步,看似是已经开始构建了,其实这是前期初始化的工作。

2、核心步骤:

2.1、找最近的点 --- 特判:还不在树内 && 距离树的距离最小。

2.2、更新最短距离:跟新与该点相连的点,跟新那些点到生成树最短的距离

3、重复步骤2 n次,直到所有顶点都加入为止。

提示:对于步骤二的第一步,怎么进行特判呢?

---有两个数组:------distN:标记此时各点距离生成树的最小距离

------stN:标记哪些点已经加入到生成树当中

声明:用prim算法构图是要存图的,这里只介绍,连接矩阵 + prim算法,以及,vector + prim算法。

用洛谷的P3366【模板】最小生成树,完成代码实现:

注意看代码的注释!!!

1、连接矩阵 + prim算法:

cpp 复制代码
#include<iostream>
#include<cstring>

using namespace std;

const int N = 5010, INF = 0x3f3f3f3f;

int n, m;
int a[N][N]; // 连接矩阵
int dist[N]; // 标记各个点距离生成树的最短距离
bool st[N]; // 标记哪个点已经加入到生成树当中

int prim()
{
	//初始化
	memset(dist, 0x3f3f3f3f, sizeof dist);
	dist[1] = 0;

	int ret = 0; // 统计最终结果
	for (int i = 1; i <= n; i++) // 把所有的点加入到生成树中
	{
		//找最近的点
		int t = 0;
		for (int j = 1; j <= n; j++) // dist[]遍历找
		{
			if (dist[j] < dist[t] && !st[j]) t = j;
		}
        
        // 找到了此时的最短距离的点
		// 判断是不是连通图
		if (dist[t] == INF) return INF; // 最短距离是无穷大就代表这个点与树不是连通的

		st[t] = true;
		ret += dist[t];

		//更新最短距离
		for (int j = 1; j <= n; j++) // 枚举t能走到哪------t是起始顶点
		{
			dist[j] = min(dist[j], a[t][j]);
		}
	}

	return ret;
}

int main()
{
	cin >> n >> m;
	memset(a, 0x3f3f3f3f, sizeof a);
	for (int i = 1; i <= m; i++)
	{
		int x, y, z; cin >> x >> y >> z;

		//连接矩阵每个格子只能存1个数据,所以就要在这先把重边问题
		//解决掉
		a[x][y] = a[y][x] = min(a[x][y], z);
	}
	
	int ret = prim();

	if (ret == INF) cout << "orz" << endl;
	else cout << ret << endl;

	return 0;
}

2、vector + prim算法:

cpp 复制代码
#include<iostream>
#include<cstring>
#include<vector>

using namespace std;

typedef pair<int, int> PII; // <结束顶点,边权>

const int N = 5010, INF = 0x3f3f3f3f;

int n, m;
vector<PII> a[N]; // 该起始顶点有哪些结束顶点,她两的边权是多少
int dist[N]; // 此时各个顶点距离生成树的最小距离
bool st[N]; // 标记哪些顶点已经加入到生成树当中

int prim()
{
	//初始化
	memset(dist, 0x3f3f3f3f, sizeof dist);
	dist[1] = 0;

	int ret = 0; // 最终结果
	for (int i = 1; i <= n; i++) // 期待把n个点加入生成树中
	{
		//找最近的点 --- 找dist数组中值最小的,要他下标,下标就是点
		int t = 0;
		for (int j = 1; j <= n; j++)
		{
			if (dist[j] < dist[t] && !st[j]) t = j;
		}

		// 找到一个此时意义上的最短距离点,通过其距离判断是否连通
		if (dist[t] == INF) return INF; // 不连通

		st[t] = true;
		ret += dist[t];

		//更新最短距离
		for (auto& e : a[t]) // 看作为起始顶点的t能走到哪个结束顶点
		{
            // a 结束顶点;b 边权
			int a = e.first, b = e.second; // 对于结束顶点,b 是新的一个可以选择的值,
                                           //适合的话可以替换掉
			// t -> a   权值是b
			dist[a] = min(dist[a], b); // 跟新此时结束顶点到生成树的最短距离
		}
	}

	return ret;
}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int x, y, z; cin >> x >> y >> z;

		//可以存多个,在跟新最短距离的时候解决重边
		a[x].push_back({ y, z });
		a[y].push_back({ x, z });
	}

	int ret = prim();
	if (ret == INF) cout << "orz" << endl;
	else cout << ret << endl;

	return 0;
}

2、kruskal算法

kruskal算法的核心是不断加边,当已有边加完之后,看看计数变量的值是不是 n - 1,是 就是构建成功,不是 就是构建失败,构建的是非连通图。

kruskal算法的基本思想:

1、按照边权值给边排升序

2、每次选取可选的最小边权且两端点不连通的一条边,直到所有顶点都连通

技巧:如何判断两端点是不是连通的呢?

---并查集,用并查集来维护,把已连通的顶点统一放在一个集合里面维护。

注意:用kruskal算法,并不需要用连接矩阵/vector存图,只需要用结构体把边与边的关系清晰存起来,方便按权值排序和后续判断连通等操作。

用洛谷的P3366【模板】最小生成树,完成代码实现:

cpp 复制代码
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 5010, M = 2e5 + 10, INF = 0x3f3f3f3f;

int n, m;
struct node
{
	int x, y, z;
}a[M];
int fa[N]; // 并查集管理已连通的边

bool cmp(node& a, node& b)
{
	return a.z < b.z;
}

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

int kruskal()
{
    // 1、排序
	sort(a + 1, a + 1 + m, cmp);

	int cnt = 0; // 计数变量
	int ret = 0; // 结果变量
	for (int i = 1; i <= m; i++) // 所有边都要考虑
	{
		int x = a[i].x, y = a[i].y, z = a[i].z;

		int fx = find(x), fy = find(y); // 找各自集合的父亲
		if (fx != fy) // 判断父亲是否相同 -不同-两端点不连通
		{
			cnt++;
			ret += z;
			fa[fx] = fy; // 把已连通的端点放在一个集合维护
		}
		//if (find(x) != find(y))
		//{
		//	cnt++;
		//	ret += z;
		//	fa[find(x)] = find(y);
		//}
	}
	return cnt == n - 1 ? ret : INF;
	//if (cnt != n - 1) return INF;
	//else return ret;
}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++) cin >> a[i].x >> a[i].y >> a[i].z;

	//并查集初始化
	for (int i = 1; i <= n; i++) fa[i] = i;

	int ret = kruskal();
	if (ret == INF) cout << "orz" << endl;
	else cout << ret << endl;

	return 0;
}
相关推荐
AOwhisky1 小时前
MySQL 学习笔记(第二期):SQL 语言之库表操作与数据类型
linux·运维·数据库·笔记·sql·学习·mysql
0xDevNull1 小时前
Java实战面试题(一)
java·开发语言
x_xbx1 小时前
LeetCode:20. 有效的括号
算法·leetcode·职场和发展
计算机安禾1 小时前
【算法设计与分析】第40篇:空间数据结构:KD树与四叉树的查询分析
数据结构·算法
雪的季节1 小时前
C++ 运行时多态 vs 编译时多态
开发语言
chushiyunen1 小时前
php笔记、下载安装等
开发语言·笔记·php
Xin_ye100861 小时前
C# 零基础到精通教程 - WPF 深度专题:自定义布局与性能优化
开发语言·c#·wpf
努力努力再努力wz1 小时前
【C++高阶数据结构系列】:跳表 SkipList 详解:多层索引、随机晋升与C++ 完整实现(附跳表实现的源码)
开发语言·数据结构·数据库·c++·redis·缓存·skiplist
更深兼春远1 小时前
scala基于IDEA部署
开发语言·scala·intellij-idea
AIFQuant1 小时前
贵金属投资 APP 开发:实时报价、图表、提醒与交易数据全链路
开发语言·前端·websocket·金融·web app