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

时间:2022-07-08 09:53:33

普利姆(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
对应的图:

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

测试输出:

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