图的最小生成树(MST)之Prim算法

普利姆(Prim)算法(只与顶点相关)

算法描述:

普利姆算法求最小生成树时候,和边数无关,只和定点的数量相关,所以适合求稠密网的最小生成树,时间复杂度为O(n*n)。

算法过程:

1.将一个图的顶点分为两部分,一部分是最小生成树中的结点(A集合),另一部分是未处理的结点(B集合)。

2.首先选择一个结点,将这个结点加入A中,然后,对集合A中的顶点遍历,找出A中顶点关联的边权值最小的那个(设为v),将此顶点从B中删除,加入集合A中。

3.递归重复步骤2,直到B集合中的结点为空,结束此过程。

4.A集合中的结点就是由Prim算法得到的最小生成树的结点,依照步骤2的结点连接这些顶点,得到的就是这个图的最小生成树。

为了实现Prim算法,假定对每一个不在A中的顶点v,都有一个配对顶点near(v),使得near(v)属于A,且cost(near(v),v)是所有可选的near(v)中代价最小的,假定(v,w)不属于E,那么cost(v,w)为正无穷。在Prim算法的每一步中,选取顶点v,使得cost(near(v),v)最小,且v不属于A。采取这种选择顶点的策略可以在O(n^2)时间内实现Prim算法。

代码(一种实现,将正无穷高换成零,修改判断的标准):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;

#define MAXN 110
int map[MAXN][MAXN], leastcost[MAXN];
bool visit[MAXN];
int nodenum, sum;

bool judge(int *a,int *b)
{
	if (*a == 0 )
	{ 
		if (*b == 0)
			return false;
		else
			return true;
	
	}
	else
	{
		if(*b == 0)
			return false;
		else
			if (*a > *b)
				return true;
			else
				return false;
	}
}
void prim(int nodenum)
{
	int temp, k;
	sum = 0;
	memset(visit, false, sizeof(visit)); //初始化visit
	visit[1] = true;  //置为true表示加入MST 
//	printf("->%d\n",1);
	for(int i = 1; i <= nodenum; ++i) //初始化leastcost[i]				 
		leastcost[i] = map[1][i];
	for(int i = 1; i <= nodenum; ++i)//找生成树集合点集相连最小权值的边
	{
		temp = 0;
		int s = 0;
		for(int j = 1; j <= nodenum; ++j)
			if(!visit[j] && judge(&temp , &leastcost[j]))
				temp = leastcost[k = j];
		if(temp == 0) break;
		for(s=1;s<=nodenum;s++)
			if(visit[s] && temp == map[s][k])
				break;
		visit[k] = true; //加入最小生成树集合
		
		printf("%d -> %d\n",s,k);
		sum += temp;//记录权值之和
		for(int j = 1; j <= nodenum; ++j) //更新leastcost数组
			if(!visit[j] && judge(&leastcost[j] ,&map[k][j]))
				leastcost[j] = map[k][j];
	}
}

int main()
{
	int a, b, weight, vertexnum,edgenum;
	scanf("%d%d",&vertexnum,&edgenum);
	memset(map, 0, sizeof(map));
	for(int i = 1; i <= edgenum; ++i) //输入边的信息
	{
		scanf("%d%d%d", &a, &b, &weight);
		map[a][b] = map[b][a] = weight;
	}
	prim(vertexnum);
	printf("%d\n", sum); //最小生成树权值之和

	return 0;
}

map初始化为某个大值(伪INF):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;

#define INT_MAX 0x3F3F3F3F
#define MAXN 110
int map[MAXN][MAXN], leastcost[MAXN];
bool visit[MAXN];
int nodenum, sum;


void prim(int nodenum)
{
	int temp, k;
	sum = 0;
	memset(visit, false, sizeof(visit)); //初始化visit
	visit[1] = true;  //置为true表示加入MST 
	//leastcost[] 表示 MST到 MST-V的最短距离 
	for(int i = 1; i <= nodenum; ++i)  
		leastcost[i] = map[1][i];
	for(int i = 1; i <= nodenum; ++i)//找生成树集合点集相连最小权值的边
	{
		temp = INT_MAX;
		int s = 0;
		for(int j = 1; j <= nodenum; ++j)
			if(!visit[j] && temp > leastcost[j])
				temp = leastcost[k = j];
		if(temp == INT_MAX) break;
		for(s=1;s<=nodenum;s++)
			if(visit[s] && temp == map[s][k])
				break;
		visit[k] = true; //加入最小生成树集合
		
		printf("%d -> %d\n",s,k);
		sum += temp;//记录权值之和
		for(int j = 1; j <= nodenum; ++j) //更新leastcost数组
			if(!visit[j] && leastcost[j] > map[k][j])
				leastcost[j] = map[k][j];
	}
}

int main()
{
	int a, b, weight, vertexnum,edgenum;
	scanf("%d%d",&vertexnum,&edgenum);
	memset(map, INT_MAX, sizeof(map));
	for(int i = 1; i <= edgenum; ++i) //输入边的信息
	{
		scanf("%d%d%d", &a, &b, &weight);
		map[a][b] = map[b][a] = weight;
	}
	prim(vertexnum);
	printf("%d\n", sum); //最小生成树权值之和

	return 0;
}
注意:这里INT_MAX设置为0x3f3f3f3f,是因为memset()以字节为单位填充map,即以INT_MAX的末尾的一个字节0x3f填充每一个字节,若是以INT_MAX中最后一个字节的第一位为1,假设大于0x7F,那么填充得到的整型值将是一个负值(4个字节一起解释为一个整型,首位符号位1,那么该整型值为负)。所以不能用“limits.h”中的INT_MAX(0x7FFFFFFF)作为此处memset(map,INT_MAX,sizeof(map))的实参。若是实在需要标准库INT_MAX,可以用for循环逐个赋值。

测试输入:

7 9
1 2 28
1 6 10
2 3 16
2 7 14
3 4 12
4 5 22
4 7 18
5 6 25
5 7 24
对应的图:

测试输出:

1 -> 6
6 -> 5
5 -> 4
4 -> 3
3 -> 2
2 -> 7
99
REF:

1,http://blog.csdn.net/niushuai666/article/details/6689295

2,数据结构(C语言版) Ellis Horowitz





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值