[算法系列]图论01-最小生成树

218 阅读2分钟

最小生成树

在开始本节之前,我们必须先了解几个概念:

极小连通子图: 若一个图是图G的连通子图,该图再删去任意一条边,该图将不再连通,满足这个性质的叫极小连通子图

生成树: 指的是包含图G所有顶点的极小连通子图

最小生成树: 各边权值最小的生成树

求解最小生成树问题的两大常用算法是Kruskal算法和Prim算法

其中Kruskal算法适用于稀疏图,Prim算法适用于稠密图

Kruskal算法

算法步骤:

1.将所有的边按权值大小进行升序排列

2.依次遍历排序好的边的集合,每次遍历到一条边的时候,判断其是否会和已经遍历的边构成环,如果不会,加入结果集合

算法原理: 贪心

难点:如何判断是否构成环?

各位思考完上面的问题,就可以看看下面的代码实现了

代码实现:

例题:给出顶点数n,边数m,要求算出最小生成树的路径长度

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
struct node{             //定义边的结构体,包含顶点和权值
    int x,y,z;
    bool operator<(const node& b)const{
        return z<b.z;
    }
}edge[N];
int fa[N],n,m,ans;
int find(int x){
    if(x==fa[x])return x;
    return fa[x]=find(fa[x]);
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>edge[i].x>>edge[i].y>>edge[i].z;
    }
    sort(edge+1,edge+1+m);
    for(int i=1;i<=n;i++){
        fa[i]=1;
    }
    for(int i=1;i<=m;i++){
        int x=find(edge[i].x);
        int y=find(edge[i].y);
        if(x==y)continue;
        fa[x]=y;
        ans+=edge[i].z;
    }
    cout<<ans<<endl;
    return 0;
}

笔者判断构成环的办法是:并查集

Prim算法

算法步骤:

仍然采用贪心策略,每次我们都选择于所选点连接后权值最小的边,有些类似Dijkstra算法

不优化的话时间复杂度是O(n^2),使用优先队列优化是O(nlogn)

代码实现:

例题:沿用上一题

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
typedef pair<int,int> pii;
int ver[N],ne[N],edge[N],head[N],tot;
int n,m,ans;
int dis[N];//dis[i]就表示第i的点与已选集合的最短距离
int vis[N],cnt;
void add(int u,int v,int val){
    ver[++tot]=v; //这里表示当前tot值代表的顶点
    edge[tot]=val; //这里设定权值
    ne[tot]=head[u]; //这里以及下面相当于链表的头插法
    head[u]=tot; 
}
priority_queue<pii,vector<pii>,greater<pii> >q;
void prim(){
    dis[1]=0;         //pair这里存储的第一个分量是已经选取的点于其余各点最小权值
    q.push({0,1});    
    while(!q.empty()&&cnt!=n){
        int d=q.top().first;
        int u=q.top().second;
        q.pop();
        if(vis[u])continue;
        cnt++;
        ans+=d;
        vis[u]=1;
        for(int i=head[u];i;i=ne[i]){
            int v=ver[i];    
            if(edge[i]<dis[v]){
                dis[v]=edge[i];
                q.push({dis[v],v});
            }
        }
    }
}
int main(){
    memset(dis,0x3f3f3f3f,sizeof dis);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,z);
    }
    prim();
    cout<<ans<<endl;
    return 0;
}

各位可以画图来模拟一下上面的过程就会比较好理解,如下图:

image.png