Prim:
贪心准则:加入后仍形成树,且耗费最小
算法过程:从单一顶点的树T开始,不断加入耗费最小的边(u,v),使T∪{(u,v)}仍为树(u,v中有一个已经在T中,另一个不在T中)
void prim()
{
priority_queue<PII,vector<PII>,greater<PII>>q;//小根堆维护最小边值
vis[1]=1;//初始任意加一个点到T中
for(int i=head[1];i;i=ed[i].next)//与之相邻的边加入队列
{
int x=ed[i].x,w=ed[i].w;
q.push({w,x});
}
while(q.size())
{
auto p=q.top();
q.pop();
int x=p.second,w=p.first;
if(vis[x])continue;//如果当前最小边连接的点已在树T中,continue
ans+=w;//否则最小生成树值加上当前边权值
vis[x]=1;//标记,即把这个点加入到数T中
for(int i=head[x];i;i=ed[i].next)//与之相连的边放入队列
{
int x=ed[i].x,w=ed[i].w;
q.push({w,x});
}
}
}
小优化:在添加边到队列中时,如果添加的边并没有减少连接的点到树的距离时,可以不用添加到队列中。
int dis[100010];//表示每个点到树的距离
void prim()
{
priority_queue<PII,vector<PII>,greater<PII>>q;
memset(dis,0x7f,sizeof(dis));
vis[1]=1;
for(int i=head[1];i;i=ed[i].next)
{
int x=ed[i].x,w=ed[i].w;
if(w<dis[x]){
q.push({w,x});
dis[x]=w;
}
}
while(q.size())
{
auto p=q.top();
q.pop();
int x=p.second,w=p.first;
if(vis[x])continue;
ans+=w;
vis[x]=1;
for(int i=head[x];i;i=ed[i].next)
{
int x=ed[i].x,w=ed[i].w;
if(w<dis[x]){
q.push({w,x});
dis[x]=w;
}
}
}
}
//代码类似dij,dij为到源点的最小值,此为到树的最小值
复杂度:(n+m)logm
Kruskal算法:
思想:贪心选取最短的边来组成一颗最小的生成树
做法:先将所有的边排序,然后利用并查集作判断(这条边连接的两个点属于两个集合)来优先选择最小的边,直到建成一颗生成树。
struct no
{
int x,y,z;
bool operator<(const no &a)const {
return z<a.z;
}
}ed[200010];
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
for(int i=1;i<=m;i++)
{
cin>>ed[i].x>>ed[i].y>>ed[i].z;
}
sort(ed+1,ed+1+m);
for(int i=1;i<=m;i++)
{
int fx=find(ed[i].x);
int fy=find(ed[i].y);
if(fx==fy)continue;
ans+=ed[i].z;
fa[fx]=fy;
}
小优化:可以用一个变量cnt记录已经加入边的数量,cnt==n-1时结束循环。
复杂度:m*logm