【ACM程序设计】最小生成树 Prim算法


最小生成树

● 最小生成树的定义是给定一个无向图,如果它任意两个顶点都联通并且是一棵树,那么我们就称之为生成树(Spanning Tree)。如果是带权值的无向图,那么权值之和最小的生成树,我们就称之为最小生成树(MST, Minimum Spanning Tree)。

● 求最小生成树的算法有很多,可以用Prim, Kuskual, Boruvka, 甚至遗传算法。这里介绍较为基础的两种Prim算法和Kuskual算法。

Prim算法

? 我们先建立两个点集,分别表示已经被加入到生成树中的点和还没有被加入到点集中的点分别记为集合V和集合T,一开始所有的点都在T集合中。

(1)我们将任意一个点从集合T置入集合V中。

(2)我们再在与集合V中的点连接的所有集合T的点中,找到一个点,使得这个点与集合V中的点的边最小,将这个点从集合T移动到集合V中。

(3)不断重复步骤(2),使得最终V包含所有图中的点。

P54-5.Prim算法_哔哩哔哩_bilibili

//sum使用引用可以把数值传出函数
void Prim(int n, float MGraph[][n], int v0, float& sum)
{
	int lowCost[n], vSet[n];
	int v, k, min;
	for (int i = 0; i < n; i++)
	{
		lowCost[i] = MGraph[v0][i]; //将首个要访问的节点与其他节点的距离填入lowcost数组
		vSet[i] = 0; //所有节点都没有访问过
	}
	v = v0; //当前节点设置为初节点
	vSet[v] = 1; //初节点设置为访问过
	sum = 0;
    //已经访问过一个节点了,要遍历剩下的n-1个节点
	for (int i = 0; i < n - 1; i++)
	{
        //要寻找到lowcost数组中的最小值
		min = INF;
		for (int j = 0; j < n; j++)
		{
            //lowcost最小值且没有遍历过的
            if(vSet[j] == 0 && lowCost[j] < min)
            {
			    min = lowCost[j];
			    k = j; //记录下最小值的序号
            }
		}
		vSet[k] = 1; //把那个最小路径的节点记录已访问
		v = k; //当前节点设置为最小路径节点
		sum += min; 
        //更新lowcost数组
		for (int j = 0; j < n; ++j)
		{
             //将未访问过的节点且是新节点下一个位置比之前lowcost小的节点更新
			if (vSet[j] == 0 && MGraph[v][j] < lowCost[j])
				lowCost[j] = MGraph[v][j];
		}
	}
}

P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

输入格式

第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边。

接下来 M 行每行包含三个整数 Xi,Yi,Zi,表示有一条长度为 Zi 的无向边连接结点 Xi,Yi,Zi

输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz

输入输出样例

输入

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

输出

7

输入

4 3
1 2 2
1 3 2
2 3 4

输出

orz

说明/提示

样例解释:

img

所以最小生成树的总边权为 2+2+3=7。

根据上述学习代码写出的题解:

#include 
int MGraph[5001][5001];
int vSet[5001];
int lowCost[5001];
int N;
#define INF 10000;
int Prim(int v0)
{
	int v, k=0, min;
	for (int i = 1; i <= N; i++)
	{
		lowCost[i] = MGraph[v0][i]; 
		vSet[i] = 0; 
	}
	v = v0; 
	vSet[v] = 1; 
	int sum = 0;
	for (int i = 2; i <= N; i++)
	{
		min = INF;
		for (int j = 1; j <= N; j++)
		{
			if (vSet[j] == 0 && lowCost[j] < min)
			{
				min = lowCost[j];
				k = j;
			}
		}
		vSet[k] = 1; 
		v = k; 
		sum += min;
		for (int j = 1; j <= N; ++j)
		{
			if (vSet[j] == 0 && MGraph[v][j] < lowCost[j])
				lowCost[j] = MGraph[v][j];
		}
	}
	for (int i = 1; i <= N; i++)
		if (vSet[i] == 0) return -1;
	return sum;
}

int main(void) {
	int i, j, k, dist;
	int M;
	scanf("%d %d", &N, &M);
	for (i = 1; i <= N; i++) {
		for (j = 1; j <= N; j++) {
			MGraph[i][j] = INF;
			if (i == j) MGraph[i][j] = 0;			
		}
	}
	for (i = 1; i <= M; i++) {
		scanf("%d %d %d", &j, &k, &dist);
		MGraph[j][k] = dist;
		MGraph[k][j] = dist;	
	}
	int res = Prim(1);
	if (res == -1) printf("orz");
	else printf("%d", res);
	return 0;
}

课上给的题解,照着前面的思路改了变量名加了注释。

#include 
int MGraph[5001][5001];
int vSet[5001];
int lowCost[5001];
int N;
void Prim(int v0)
{
	int i, j, k, cnt;
	int mindis, v;
	mindis = 0, v = v0;
	for (i = 1; i <= N; i++)
	{
		vSet[i] = 0; //vSet置0
		lowCost[i] = -1; //lowCost置-1
	}
	lowCost[v] = 0; //初始节点的lowcost置0
	//开始遍历剩下的n-1个节点
	for (cnt = 1; cnt < N && v; cnt++) {
		vSet[v] = 1;
		j = v, v = 0, mindis = 1000000001; 
		//遍历每个未访问过的节点更新lowcost数组
		for (i = 1; i <= N; i++) 
		{
			if (vSet[i]==0) 
			{
				if (MGraph[j][i] != -1) 
				{
					if (lowCost[i] == -1 || MGraph[j][i] < lowCost[i]) 
						lowCost[i] = MGraph[j][i];
				}
				if (lowCost[i] != -1 && mindis > lowCost[i]) 
				{
					mindis = lowCost[i];
					v = i;
				}
			}
		}
	}
	if (cnt != N) printf("orz\n");
	else {
		long long ans = 0;
		//累加lowcost数组即可得到最短路径
		for (i = 1; i <= N; i++) 
			ans += lowCost[i];
		printf("%lld\n", ans);
	}
	return;
}

int main(void) {
	int i, j, k, dist;
	int M;
	scanf("%d %d", &N, &M);
	//将初始邻接矩阵置-1
	for (i = 1; i <= N; i++) {
		for (j = 1; j <= N; j++) {
			MGraph[i][j] = -1;
			if (i == j) {
				MGraph[i][j] = 0;
			}
		}
	}
	for (i = 1; i <= M; i++) {
		scanf("%d %d %d", &j, &k, &dist);
		if (MGraph[j][k] == -1 || MGraph[j][k] > dist) {
			MGraph[j][k] = dist;
			MGraph[k][j] = dist;
		}
	}
	Prim(1);
	return 0;
}