图的生成树
从连通图中删去若干边,来扣出一个树
前者是没有加边权的情况,这个时候随便删都没事,但是如果是第二张图,要求权值最小,则只能删去边权位为5的边
经典例题
用以求解此类的问题的算法
kurskal算法
两点之间互有边长
先排序边长(sort就行)
过后就得到了一个边权从小到大的排序
贪心
每条边的存在使得两个点之间联通
如果加入图中不会使得这个图产生环,那么久加入
树中是不可能存在环的
想象一下你和你的爷爷是直系亲属是什么感觉
上图中红色的边的加入有两个不合理处
1:使得这棵树产生了环
2:2和3本来已经有公共祖先让它们进行连通了
所以说这条边是不合理的
加入这条边是否会产生环
两个本身连通的点连通后势必会存在环
算法的复杂度是O(|E|log|V|)
算法排序之后的行为就可以用并查集来维护
小小的优化?
对于一棵有n个点的树,至多有n-1条边
所以可以使用一个cnt变量来统计边数,达到了n-1立马跳出
图不是连通图
如果cnt没有达到n-1,则需要直接输出无解
kurskal算法模板
#include<bits/stdc++.h>
using namespace std;
int fa[200007],rankk[200007];
long long int n,m;
void init(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
rankk[i]=1;
}
}
int find(int x){
if(fa[x]==x)return x;
else fa[x]=find(fa[x]);//路径压缩
}
void unite(int x,int y){
x=find(x);
y=find(y);
if(x==y)return;
if(rankk[x]<rankk[y])fa[x]=y;//高度优化
else{
fa[y]=x;
if(rankk[x]==rankk[y]) rankk[x]++;
}
}
int same(int x,int y){
return find(x)==find(y);
}
struct edge{//边
int u,v,cost;
}e[200007];
int cmp(edge e1,edge e2){
return e1.cost<e2.cost;
}
long long int kruskal(){
sort(e+1,e+1+m,cmp);
int res=0,side=0;
for(int i=1;i<=m;i++){
if(same(e[i].u,e[i].v)==0){
unite(e[i].u,e[i].v);
res+=e[i].cost;
++side;
}
}
if(side==n-1)return res;
else return -1;
}
int main(){
cin>>n>>m;
init(n);
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].cost;
}
long long int ans=kruskal();
if(ans==-1)cout<<"orz";
else cout<<ans;
return 0;
}
次小生成树和最大生成树
最大生成树
只需要将排序函数cmp中的<改成>
int cmp(edge e1,edge e2){
return e1.cost<e2.cost;
}
或者将边权输入是*-1,然后取出来的时候转回来
次小生成树
(严格)
最小生成树的边权总和为10
严格次小生成树比安全综合就必须>10
(不严格)
可以和最小生成树一样
如果不产生环就加边
求完最小生成树之后,找一个不输入最小生成树的边加入图中,势必会使得图中产生环
替换掉环中除了该边以外权值最大的一条边,至此,得到一个新的生成树
找出新的生成树中最小的就是次小生成树