最小生成树问题一般对应无向图,一般都有m≤n*n,所以mlogm和mlogn差不多,所以我们一般用kruskal而不常用堆优化的prim
1.朴素版Prim
o(n2)
基本思路
初始化 dis[i] <- 正无穷大
for (i=0;i<n;i++) n次迭代,因为要加入n个点到集合中
t <- 找到集合外距离最近的点
用 t 更新 其他点 -> “集合” 的距离 //Dij是来更新到起点的距离
st[t]=true;//把t加到集合里
优化方式和dij差不多
例题
稠密图
输入
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出
6
代码
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
const int maxn=510;
const int INF=0x3f3f3f3f;
using namespace std;
int n,m;
int mp[maxn][maxn],dis[maxn];//dis表示点到集合的距离
bool st[maxn];
int prim()
{
memset(dis,0x3f,sizeof(dis));
int ans=0;
for(int i=0;i<n;i++)//进行n次迭代
{
int t=-1;
for(int j=1;j<=n;j++)·//开始遍历点,寻找距离集合最短的点
if(!st[j]&&(t==-1||dis[t]>dis[j]))
t=j;
if(i&&dis[t]==INF)//不是第一个点且当前距离集合最近的点都是正无穷
return INF;//说明最短路不存在
if(i)
ans+=dis[t];//不能把初始化时的INF加进去
st[t]=true;
for(int j=1;j<=n;j++)
dis[j]=min(dis[j],mp[t][j]);//用当前加入的这个点t去更新其他点到集合的距离
}
return ans;
}
int main()
{
cin>>n>>m;
memset(mp,0x3f,sizeof(mp));
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
mp[a][b]=mp[b][a]=min(mp[a][b],c);//无向图,有重边的话保存最小的那条边
}
int ans=prim();
if(ans==INF)
cout<<"impossible"<<endl;
else
cout<<ans<<endl;
return 0;
}
和dijstra算法极为相似,只是更新那里有一点不同: dis[j]=min(dis[j],mp[t][j]);
注意:最小生成树时不能有自环的
所以如果存在一个点它有负权自环,是不应该加进去的。如果for更新放在求权值和之后,可能会导致把负权自环更新过的dis加进去,这里的顺序要注意先进行累加。
以下写法会导致两次调用prim(),初始化会有问题,st会没有初始化到
if(prim()==INF) puts(“impossible”);
else cout<<prim()<<endl;
堆优化就是用 堆来维护dis[],把时间复杂度降到O(1)
思路和代码都比kruskal麻烦,所以不常用。
2.Kruskal
O(mlogm)
时间主要花在排序上,但排序的常数很小,就是一个logn进行运算的次数很少,所以通常时间上会表现得比较理论上好。
基本思路
1.将所有边按权重从小到大排序//O(mlogm) 算法的瓶颈
2.枚举每条边 a,b,权重c //O(m)
if a,b不连通 //并查集判环
将这条边加到集合中
例题
输入
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出
6
代码
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
const int maxn=1e5+5;
const int INF=0x3f3f3f3f;
using namespace std;
int n,m;
struct node{
int a,b,w;
bool operator < (const node &t)const
{
return w<t.w;
}
}edges[2*maxn];
int p[maxn];//用来存储并查集的父亲节点
int find(int x)//找到它们的祖宗结点
{
if(p[x]!=x)
p[x]=find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges,edges+m);//这里采用了重载运算符方式,也可以单独去写一个cmp函数作为第三个参数
for(int i=1;i<=n;i++)//初始化并查集,刚开始时,每个人的父节点都是它自己
p[i]=i;
int ans=0,cnt=0;
for(int i=0;i<m;i++)
{
int a,b,w;
a=edges[i].a,b=edges[i].b,w=edges[i].w;
a=find(a),b=find(b);
if(a!=b)//说明不连通
{
p[a]=b;//合并起来,让a的父节点变成b
ans+=w;
cnt++;//当前集合中边数+1
}
}
if(cnt<n-1)//一共n个点,如果没有n-1条边,说明不能全部连通
return INF;
return ans;
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)
{
int a,b,w;
cin>>a>>b>>w;
edges[i]={a,b,w};
}
int ans=kruskal();
if(ans==INF)
cout<<"impossible"<<endl;
else
cout<<ans<<endl;
return 0;
}