定义:在无向连通图中边权和最小的生成树为最小生成树。生成树的定义是在一个无向图中包含所有节点且边数最少的连通子图。
Kruskal算法
考虑一种贪心,我们将边权从小到大排列,依次将边加入生成树中,如果此次加边形成了环,则扔掉这条边。
我们可以轻易证明此贪心的正确性,若在某次加入中此条边不是最小生成树的一条边,那后面这两个点最终也要连起来,这两个点连起来的路径和这条边会组成一个环,这个环中一定有边权大于这条边的边权(因为一定有比这条边后加入的边,不然这条边加入就会组成环被扔掉),用这条边替换,图依旧联通,但是总权值减少了,所以要是不选取这条边,接下来构成的生成树一定不是最少的。
代码实现我们需要用并查集查询两点是否联通和将两点连接。
点击查看代码
struct Node{
int u,v,w;
}g[N];
bool cmp(Node x,Node y){
return x.w<y.w;
}
int fa[N],n,m,ans;//m表示边数,n表示点数,ans表示总权值
//标准并查集
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
if(find(x)!=find(y)) fa[find(x)]=find(y);
}
void kruskal(){
int tot=0;
sort(g+1,g+1+m,cmp);
for(int i=1;i<=m;i++){
if(find(g[i].u)!=find(g[i].v)){//如果没有形成环就加入
tot++;
ans+=g[i].w;
if(tot>=n-1){
cout<<ans;
return;//已建成最小生成树,返回
}
merge(g[i].u,g[i].v);
}
}
cout<<"no answer";//若未返回就是找不到
}
Prim算法
从一个节点开始,选择距离最小的节点,类似Dijkstra算法的操作,理论上来说在稠密图时间复杂度优于Kruskal算法,但是Prim算法的常数十分大,实际不一定跑下来更快。
点击查看代码
struct Node{
int u,v,w;
};
vector<Node>g[N];
struct T{
int d,u;
friend bool operator<(T a,T b){
return a.d>b.d;
}
};
priority_queue< T > q;
int n,m,dis[N],vis[N];
int prim(){
int ans=0;
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push(T{0,1});
while(!q.empty()){
T t=q.top();
q.pop();
int d=t.d,u=t.u;
if(vis[u]) continue;
vis[u]=1;
ans+=d;
for(Node e:g[u]){
int v=e.v;
if(!vis[v]) q.push(T{e.w,v});
}
}
return ans;
}