最小生成树–prim算法
首先说明,Dijkstra算法和prim算法实际上是相同的思路,只不过是数组d[]的含义不同。
详见 算法笔记 P404
prim算法的基本思想是对图G设置集合S(就是个巨型防护罩),用来存放已经被访问的顶点(就是被攻占的城市),然后执行n次下面的两个步骤:
1.每次从集合V-S(就是未被攻占的城市)中选择与集合S(巨型防护罩)最近的一个顶点(记为u),访问(即攻占)u并将其加入集合S(巨型防护罩)的最短距离。
2.令顶点u作为集合S与集合V-S连接的接口(就是把当前攻占的城市作为巨型防护罩与外界的接口,衍生的触手,刚入会就要做贡献),优化从u能到达的未访问顶点v(未攻占城市)与集合S(巨型防护罩)的最短距离。
prim算法的具体实现:
prim算法需要实现两个关键的概念:
集合S的实现
顶点Vi(0<=i<=n-1)与集合S(巨型防护罩)的最短距离
1.集合S的实现方法和Dijkstra中相同,即使用一个bool型数组vis[]表示顶点是否已被访问。其中vis[i]==true表示顶点Vi 已被访问,vis[i]== false 则表示顶点Vi未被访问。
2.令int型数组d[]来存放顶点Vi与集合S(巨型防护罩)的最短距离。初始时除了起点s的d[s]赋为0,其余顶点都赋为一个很大的数来表示INF,就是不可达。
对于最小生成树问题,如果仅是求最小边权之和,在prim算法中可以随意指定一个顶点为初始点。
//prim算法伪代码 模板
//G为图,一般设为全局变量。数组d 为顶点与集合S 的最短距离
Prim( G,d[] )
{
初始化;
for(循环n次)
{
u = 是d[u]最小的还未被访问的顶点的标号;
记u已被访问;
for(从u出发能到达的所有顶点v)
{
if( v未被访问 && 以u为中介点使得v与集合S的最短距离d[v]更优 )
{
将G[u][v]赋值给v与集合S的最短距离d[v];
}
}
}
}
//prim算法 完整程序模板
//最大顶点数
const int MAXV=1000;
//设置一个无穷大
const int INF=0x3fffffff;
//邻接矩阵版
int n,G[MAXV][MAXV];
//顶点与集合S的最短距离
int d[MAXV];
//标记数组
bool vis[MAXV]={false};
//默认0号为初始点,函数返回最小生成树的边权之和
//fill函数将整个d数组赋为INF
fill(d,d+MAXV,INF );
//只有0号顶点到集合S的距离为0,其余全为INF
d[0]=0;
//这里不一样!!!存放最小生成树的边权之和
int ans=0;
//循环n次
for(int i=0;i<n;i++)
{
//u使d[u]最小,MIN存放最小的D[u]
int u=-1,MIN=INF;
for(int j=0;j<n;j++)
{
if( vis[j]==false && d[j]<MIN )
{
u=j;
MIN=d[j];
}
}
//找不到小于INF的d[u],则剩下的顶点和集合S不连通
if(u==-1)
{
return -1;
}
//标记u已被访问
vis[u]=true;
//这里不一样!!!将与集合S距离最小的边加入最小生成树
ans+=d[u];
for(int v=0;v<n;v++)
{
//v未访问 && u能到达v && 以u为中介点可以使v离集合S更近
if( vis[v] == false && G[u][v]!=INF
&& G[u][v]<d[v] )
{
//将G[u][v] 赋值为 d[v]
d[v]=G[u][v];
}
}
//返回最小生成树的边权之和
return ans;
}
//邻接表版
struct Node{
//v为边的目标顶点,dis为边权
int v,dis;
};
//图G,adj[u]存放从顶点u出发可以到达的所有顶点
vector<Node> adj[MAXV];
//n为顶点数,图G使用邻接表实现,MAXV为最大顶点数
int n;
//这里不一样!!!顶点与集合S的最短距离
int d[MAXV];
bool vis[MAXV]={false};
//默认0号为初始点,函数返回最小生成树的边权之和
int prim()
{
//fill函数将整个d数组赋为INF
fill( d,d+MAXV,INF );
//只有0号顶点到集合S的距离为0,其余全为INF
d[0]=0;
//存放最小生成树边权之和
for(int i=0;i<n;i++)
{
int u=-1,MIN=INF;
for(int j=0;j<n;j++)
{
if( vis[j]==false && d[j]<MIN )
{
u=j;
MIN=d[j];
}
}
if(u==-1)
{
return -1;
}
vis[u]=true;
//这里不一样!!!将与集合S距离最小的边加入最小生成树
ans+=d[u];
//只有下面 的这个for循环和邻接矩阵的写法不同
for(int j=0;j<adj[u].size();j++)
{
//通过邻接表直接获得u能到达的顶点v
int v=adj[u][j].v;
if( vis[v]==false && adj[u][j].dis<d[v] )
{
d[v]=adj[u][j].dis;
}
}
}
//返回最小生成树的边权之和
return ans;
}