定义
1、生成树
生成树本质上是一个图的子图,它的特性如下
- 它是一颗无根树
- 它包含原图的所有节点
听着可能有点抽象,说大白话,就是他是一个图删掉若干条边形成的树
当然,一个图的生成树可能有很多
2、最小生成树
在若干个生成树中边权和最小的,该树成为这个图的最小生成树
算法一 Prim(加点法)
算法分析
加点法,顾名思义,每次找到最优的点加入ans,加n次就形成了一个最小生成树
该算法基于贪心思想,核心在于维护d数组:d[x]表示点x到已经在最小生成树中的点中距离最短(边权最小)的点的距离。
n次找点中,每次找到未加入最小生成树的点中d[i]最小的值加入即可
如何维护d数组呢
就在找到最优点x后,遍历x子节点,用点x到其距离更新d数组即可
优化
显然这样时间复杂度来到了n^2,非常难受
这个算法很像最短路dijkstra,所以,同样的,用堆优化找最小值那一步
最后的时间复杂度就是mlogn啦
结合代码理解吧~
代码
void Prim(int s){//起点编号s
memset(d,0x3f,sizeof(d));//求min先赋最大
memset(vis,0,sizeof(vis));//标记数组
d[s]=0;
q.push(node(s,0));
while(!q.empty()){
int u=q.top().first;//取出最优点
q.pop();
if(vis[u]){
continue;
}
ans+=d[u];
vis[u]=1;//标记
for(int i=0;i<G[u].size();i++){//遍历子节点
int v=G[u][i].v;
long long w=G[u][i].w;
if(d[v]>w&&!vis[v]){
d[v]=w;
q.push(node(v,w));//更新d数组
}
}
}
}
算法二 Kruskal(加边法)
个人认为此思路更容易理解
算法分析
既然可以加点,那也可以加边。
那么如何加边呢,很容易就想到排序。所以将所有边按权值先排个序。
但如果仅按此累加的话,稍微思考便会发现,会出现环(也可以说是多余边,将连通的节点再连起来)。
所以,我们需要一个东西在加边时判断两点是否已经直接或间接的连通了。
——并查集。
在每次加边时维护并查集,加边前判断,n个点加n-1条边,思路达成!
代码
#include<bits/stdc++.h>
using namespace std;
const int M=2e5+10,inf=1e9+10;
int n,m,tt,ans,f[M];
struct edge{
int v,u,w;
}edg[M*2];
bool cmp(edge x,edge y){
return x.w<y.w;//按边权排序
}
int findd(int x){
if(x==f[x]) return x;
return f[x]=findd(f[x]);//并查集基操
}
void kruskal(){
for(int i=1;i<=m;i++){
int f1=findd(edg[i].u),f2=findd(edg[i].v);
if(f1==f2) continue;//是否已经连通
f[f2]=f1;//维护并查集
ans+=edg[i].w;//记录答案
tt++;
if(tt==n-1) break;//加n-1条边
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>edg[i].u>>edg[i].v>>edg[i].w;
}
sort(edg+1,edg+m+1,cmp);//排序
for(int i=1;i<=n;i++) f[i]=i;//并查集初始化
kruskal();
cout<<ans;
return 0;
}
算法时间复杂度易得为O(mlogm)
总结
两种算法本人更喜欢Kr(写起来更方便),分析时间复杂度,Prim在处理稠密图(尤其是完全图)时更有优势。
~~~---:)))