Prim算法理解,可以看这篇文章https://blog.youkuaiyun.com/yeruby/article/details/38615045
其实就是每次从当前树中外选取一个离树最近且不构成环的点,同时sum记录权值,然后把这个点加入树中,直到所有节点都被访问过,最小生成树生成成功,输出最小生成树的权值和。
下面是Prim算法的板子,和最短路有点相似,也称为“加点法”。
-
//最小生成树Prim算法
-
#include<iostream>
-
#define INF 0x3f3f3f3f
-
using
namespace
std;
-
const
int maxn =
110;
-
int n,m;
-
int G[maxn][maxn];
-
int prim()
-
{
-
int mst[maxn],sum=
0,lowcost[maxn];
-
//lowcost[i]: 表示以i为终点的边的最小权值,
-
//当lowcost[i]=0说明以i为终点的边的最小权值=0,
-
//也就是表示i点加入了MST
-
//mst[i]:表示对应lowcost[i]的起点,
-
//即说明边<mst[i],i>是MST的一条边,
-
//当mst[i]=0表示起点i加入MST
-
for(
int i =
2;i<=n;i++){
-
lowcost[i]=G[
1][i];
-
mst[i]=
1;
//初始化lowcost数组为到起始点的距离,mst数组为1(即所有点的起点都是1)
-
}
-
mst[
1]=
0;
//起始点进树
-
for(
int i =
2;i<=n;i++){
-
int minn=INF,minid=
0;
-
for(
int j =
2;j<=n;j++)
//遍历lowcost数组,查询最小值
-
if(minn>lowcost[j]&&lowcost[j]!=
0)
-
minn=lowcost[minid=j];
-
// printf("%d-%d=%d\n",mst[minid],minid,minn);
-
lowcost[minid]=
0;
//查询到的最短的点进树
-
sum+=minn;
-
for(
int j=
2;j<=n;j++){
//节点进树后更新外面的点到树的最短距离并且mst记录其起点
-
if(G[minid][j]<lowcost[j]){
-
lowcost[j]=G[minid][j];
-
mst[j]=minid;
-
}
-
}
-
}
-
return sum;
-
}
-
int main()
-
{
-
scanf(
"%d %d",&n,&m);
-
for(
int i =
1;i<=n;i++)
-
for(
int j =
1;j<=n;j++)
-
G[i][j]=INF;
//初始化
-
for(
int i =
1;i<=m;i++){
-
int a,b,cost;
-
scanf(
"%d %d %d",&a,&b,&cost);
-
G[a][b]=G[b][a]=cost;
-
}
-
int cost=prim();
-
printf(
"%d\n",cost);
-
return
0;
-
}
其实也可以用一个visited数组来标记是否被访问过。
Kruskal算法(基于贪心思想和并查集的实现)
关于算法的理解可以看这篇博客https://blog.youkuaiyun.com/luoshixian099/article/details/51908175
不过上面博客的代码实现比较复杂。
关于并查集的理解可以看这篇文章https://blog.youkuaiyun.com/niushuai666/article/details/6662911
其实算法很好理解,就是先把图中所有边按照权值从小到大给他们排个序(贪心),然后每次取一条边,判断边的起点和终点是否在一个连通分量里面,如果不在就合并,(这里关键在于连通分量的查询和合并),直到构成一棵最小生成树。
一谈到贪心,我们就不能简单的去用,还要能证明,不然说不定就错了(QAQ,就像01背包一样)
具体的证明过程可以去百度,或者看紫书356页。
下面给出伪代码的实现
-
把所有边排序,记第i小的边为e[i](i<=
1<m)
-
初始化MST为空
-
初始化连通分量为空,让每个点自成一个独立的连通分量
-
初始化sum=
0
-
for(i=
0;i<m(边的个数);i++)
-
if(e[i].u(起点)和e[i].v(终点)不在同一个连通分量中){
-
把边e[i]加入MST中
-
合并e[i].u和e[i].v所在的连通分量
-
sum+=e[i].w;(记录权值)
-
}
-
-
-
下面是代码具体实现,不过涉及到间接排序(不知道的可以百度,有点绕),其实也可以开个结构数组排序。
-
#include <iostream>
-
#include<algorithm>
-
using
namespace
std;
-
int p[
110],w[
110],r[
110],u[
110],v[
110];
-
//第i条边的两个端点序号和权值分别存储在u[i],v[i]和w[i]中
-
//排序后第i小的序号存储在r[i]中(间接排序,排序的是对象的代号而不是其本身)
-
int n,m;
-
bool cmp(const int i ,const int j)//间接排序函数
-
{
-
return w[i]<w[j];
-
}
-
int find(int x)//并查集的find
-
{
-
return p[x]==x?x:p[x]=find(p[x]);
//路径压缩
-
}
-
void init()//初始化并查集和边的序号
-
{
-
for(
int i =
1;i<=n;i++)
-
p[i]=i;
-
for(
int i =
1;i<=m;i++)
-
r[i]=i;
-
}
-
int Kruskal()
-
{
-
int ans=
0;
-
init();
-
sort(r,r+m,cmp);
//给边排序
-
for(
int i =
1;i<=m;i++){
-
int e=r[i];
-
int x=find(u[e]);
-
int y=find(v[e]);
//找出两个边所在集合的编号
-
if(x!=y){
-
ans+=w[e];
-
p[x]=y;
-
//如果在不同集合,合并
-
}
-
}
-
return ans;
-
}
-
int main(int argc, char** argv) {
-
while(
scanf(
"%d %d",&n,&m)!=EOF){
-
for(
int i =
0;i<m;i++)
-
scanf(
"%d %d %d",&u[i],&v[i],&w[i]);
-
printf(
"%d\n",Kruskal());
-
}
-
return
0;
-
}
然后就是各种习题的练习,下面给出一些经典的题和题解。
POJ 1251Jungle Roads —— POJ1251题解
POJ 1287 Networking —— POJ1287题解
POJ 2031 Building a Space Station——POJ2031题解
POJ 2421 Constructing Roads —— POJ2421题解
ZOJ1586QS Network —— ZOJ1586题解
POJ1789Truck History —— POJ1789题解