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题解