图论(三)最小生成树:Prim算法与Kruskal算法


图论系列文章

图论(一) 图的DFS与BFS

图论(二) 最短路径:Dijkstra算法与Floyd算法

图论(三)最小生成树:Prim算法与Kruskal算法

图论(四)拓扑排序与关键路径


前言

在学习了前两章的内容后,接下来学习最小生成树算法。

最小生成树的意思是将所有的顶点都连通,所有的边加起来的权值最小的图(成形为树)。此类算法广泛用于城市建设中,例如让所有的城市全部连通,使其造价最小。

  • 测试图

在这里插入图片描述

  • 最小生成树
    在这里插入图片描述

  • 测试数据

7 10
0 1 5
0 2 2
1 3 1
2 3 6
1 4 6
3 4 1
3 5 2
2 5 8
4 6 7
5 6 3

一.Prim算法

普利姆算法(Prim)可以在加权连通图中搜索最小生成树,其最小生成树包含了连通图中的所有顶点,并且其所有边的权值之和最小。其时间复杂度为O(N2),比较适合稠密图,也就是弧比较多的图。

Prim算法核心
创建新的顶点集合U,以某一个顶点为起始顶点(通常以第0个顶点开始)。然后:

  1. 找到新集合U中所有顶点的相邻的权值最小的边,并加入该边相邻的新顶点;
  2. 将新加入的顶点的相邻边更新。

重复以上1,2步骤直到所以的顶点都加入到了新的顶点集合U中。


/*
Prim算法思量:
lowcost:记录的是每个顶点所在边的权重,以及顶点是否已经被访问;0代表该顶点已经访问,INT_MAX代表暂不连通,
正常数值代表该顶点连通的最小权值边
adjvex;记录目前加入顶点的相邻顶点
*/

int N, C;//N个顶点,C条边
void Prim(vector<vector<int>>& vertex)
{
	vector<int> lowcost(N, INT_MAX), adjvex(N, 0);
	for (int i = 0; i < N; ++i)
		lowcost[i] = vertex[0][i];

	for (int i = 0; i < N - 1; ++i) //将剩下的N-1个顶点依次加入集合
	{
		//找到现有集合中权值最小的边
		int Min = INT_MAX, k = 0; //k是即将加入集合的下一个顶点,Min用来保存权值最小的边
		for (int j = 0; j < N; ++j)
		{
			if (0 != lowcost[j] && lowcost[j] < Min) //顶点j没有被访问过,找到最小权值边相连的顶点
			{
				Min = lowcost[j];
				k = j;
			}
		}

		cout << "(" << adjvex[k] << "," << k << ") ";

		//将k顶点加入集合,并且更新k顶点相邻的边
		lowcost[k] = 0;
		for (int j = 0; j < N; ++j)
		{
			if (0 != lowcost[j] && vertex[k][j] < lowcost[j])//顶点未被访问,k-j之间连通
			{
				lowcost[j] = vertex[k][j];
				adjvex[j] = k;
			}
		}
	}
}




int main()
{
	cin >> N >> C;
	vector<vector<int>> vertex(N, vector<int>(N, INT32_MAX));//由于是加权图,不连通则用INT32_MAX表示
	for (int i = 0; i < C; ++i)
	{
		int m, n, weight;
		cin >> m >> n >> weight;
		vertex[m][n] = weight;//顶点m到顶点n连通
		vertex[n][m] = weight;//无向图,顶点n到顶点m连通
	}
	for (int i = 0; i < N; ++i)
		for (int j = 0; j < N; ++j)
			if (i == j)
				vertex[i][j] = 0;
	Prim(vertex);
	return 0;
}


二.Kruskal算法

Kruskal算法较为简单,只需要不断找到权值较小的边,依次连接好顶点即可;过程中需要注意顶点之间不能够成环。时间复杂度为O(eloge),适合稀疏图,也就是边比较少的图。

算法核心

  1. 将输入的边进行存储;
  2. 找到权值最小的边,连接好首尾顶点;
  3. 通过并查集判断有没有成环;
  4. 不断重复2,3步骤直到遍历完所有的边。

不断加入权值最小的弧,并判断该弧连接的顶点是否属于并查集中的同一个根,如果不是,那么加入该顶点。

  • 并查集

为了防止最小生成树中没有环,我们需要使用到一种数据结构:并查集
并查集下标指向该顶点,存放的元素是其根,可以用来帮助我们合并两个源点不同的集合, 并且也可以当作树的路径生成来使用。

int find(vector<int> &union_find, int i)
{
    if (union_find[i] != -1)
        return find(union_find, union_find[i]);
    return i;
}
vector<int> union_find(_vertexNum, -1); //并查集

  • 实现程序

/*
Kruskal算法
*/
struct Edge
{
	int beg;
	int end;
	int weight;
	Edge(int a, int b, int c):beg(a), end(b), weight(c){}
};

int N, C;//N个顶点,C条边

//并查集查找,用于判断两个顶点是否在同一个集合中,最后返回的是集合根顶点的位置
int find(vector<int>& parent, int i)
{
	while (parent[i] != -1)
		i = parent[i];
	return i;
}

void Kruskal(vector<Edge>& edges)
{
	vector<int> parent(N, -1);//并查集,用于判断两个顶点是否在同一个集合中
	for (int i = 0; i < C; ++i) //循环遍历每一条边,因为有并查集的存在,所以可以保证不成环
	{
		int m = find(parent, edges[i].beg);
		int n = find(parent, edges[i].end);
		if (m != n)//如果根节点不相同
		{
			cout << "(" << edges[i].beg << "," << edges[i].end << ") ";
			parent[m] = n;
		}
	}
}


int main()	
{
	cin >> N >> C;
	vector<Edge> edges;//对于Kruscal算法,只需要对边进行操作即可.
	for (int i = 0; i < C; ++i)
	{
		int m, n, weight;
		cin >> m >> n >> weight;
		edges.push_back(Edge(m, n, weight));
	}
	//根据边的权值大小进行排序
	sort(edges.begin(), edges.end(), [&](Edge& lhs, Edge& rhs) {
		return lhs.weight < rhs.weight;
	});
	Kruskal(edges);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值