最小生成树 | Minimum Spanning Tree | 普里姆Prim算法 | C/C++实现

本文为普里姆算法(Prim’s Algorithm)对于求最小生成树的另一种算法:
克鲁斯卡尔算法(Kruskal’s Algorithm)请见:最小生成树 | Minimum Spanning Tree | 克鲁斯卡尔Kruskal算法 | C/C++实现

问题描述

请编写一个程序,计算给定加权图 G = ( V , E ) G=(V,E) G=(V,E)的最小生成树的各边权值之和。
输入: 第1行输入G的顶点数n。接下来n行输入表示G的 n ∗ n n*n nn邻接矩阵A。A的元素 a i j a_{ij} aij代表顶点 i i i到顶点 j j j的边的权值。另外,边不存在时记为-1。
输出: 输出G的最小生成树的各边权值总和,占1行。
限制:
1 ≤ n ≤ 100
0 ≤ a i j a_{ij} aij ≤ 2000 ( a i j ≠ − 1 时 ) (a_{ij} \neq -1 时) (aij̸=1)
a i j = a j i a_{ij}=a_{ji} aij=aji
G为连通图。

输入示例

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

输出示例

5

讲解

普里姆算法(Prim’s Algorithm)是求图 G = ( V , E ) G=(V,E) G=(V,E)最小生成树(MST)的代表性算法之一,其基本思路如下:

设图 ( V , E ) (V,E) (V,E)所有顶点的集合为V,MST中顶点的集合为T。
1.从G中选取任意顶点 r r r作为MST的根,将其添加至T。
2.循环执行下述处理直至T = V
在连接T内顶点与V - T内顶点的边中选取权值最小的边 ( p u , u ) (p_u,u) (pu,u),将其作为MST的边,并将 u u u添加至 T T T

实现这一算法的关键,在于选择边时如何保存权值最小的边。使用邻接矩阵实现的Prim算法需要准备以下变量。这里的 n = ∣ v ∣ n=|v| n=v

color[n]:color[v]用于记录v的访问状态WHITE、GRAY、BLACK。
M[n][n]:邻接矩阵,M[u][v]中记录u到v的边的权值。
d[n]:d[v]中用于记录连接T内顶点与V - T内顶点的边中,权值最小边的权值。
p[n]:p[v]用于记录MST中顶点v的父节点。

基于上述变量,可实现Prim算法的下述伪代码

prim()
	将所有顶点u的color[u]设为WHITE,d[u]初始化为INFTY
	d[0] = 0
	p[0] = -1

	while true
		mincost = INFTY
		for i从0至n-1
			if color[i] != BLACK && d[i] < mincost
				mincost = d[i]
				u = i
			
		if mincost == INFTY
			break
		
		color[u] = BLACK

		for v从0至n-1
			if color[v] != BlACK且u和v之间存在边
			if M[u][v] < d[v]
				d[v] = M[u][v]
				p[v] = u
				color[v] = GRAY

上述各步骤中,选择顶点u的操作就相当于在连接T内顶点与V - T内顶点的边中选取权值最小的边。另外,只要选定了u,边 ( p [ u ] , u ) (p[u],u) (p[u],u)即会成为构成MST的边。

在使用邻接矩阵实现的普里姆算法中,我们需要遍历图的所有顶点来确定d最小的顶点u,且整个算法的遍历次数与顶点数相等,因此算法复杂度为O(|V|^2)。如果使用二叉堆来选定顶点,效率将大大提高。

AC代码如下

#include<iostream>
using namespace std;
static const int MAX = 100;
static const int INFTY = (1<<21);
static const int WHITE = 0;
static const int GRAY = 1;
static const int BLACK = 2;

int n, M[MAX][MAX];

int prim(){
	int u, minv;
	int d[MAX], p[MAX], color[MAX];
	
	for(int i = 0; i < n; i++){
		d[i] = INFTY;
		p[i] = -1;
		color[i] = WHITE;
	}
	
	d[0] = 0;
	
	while(1){
		minv = INFTY;
		u = -1;
		for(int i = 0; i < n; i++){
			if(minv > d[i] && color[i] != BLACK){
				u = i;
				minv = d[i];
			}
		}
		if(u == -1) break;
		color[u] = BLACK;
		for(int v = 0; v < n; v++){
			if(color[v] != BLACK && M[u][v] != INFTY){
				if(d[v] > M[u][v]){
					d[v] = M[u][v];
					p[v] = u;
					color[v] = GRAY;
				}
			}
		}
	}
	int sum = 0;
	for(int i = 0; i < n; i++){
		if(p[i] != -1) sum += M[i][p[i]];
	}
	
	return sum;
} 

int main(){
	cin>>n;
	
	for(int i = 0; i < n; i++){
		for(int j = 0; j < n; j++){
			int e; cin>>e;
			M[i][j] = (e == -1) ? INFTY : e;
		}
	}
	
	cout<<prim()<<endl;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值