第十周 周结(图论算法——最小生成树)

一:基本概念

在一个连通无向图G=(V, E)中,对于其中的每条边(u,v)∈E,赋予其权重w(u, v),则最小生成树问题就是要在G中找到一个连通图G中所有顶点的无环子集T⊆E,使得这个子集中所有边的权重之和在这里插入图片描述最小。显然,T必然是一棵树,这样的树通常称为图G的生成树。

二. 最小生成树算法

通常来说,要解决最小生成树问题,通常采用两种算法:Prim算法和Kruskal算法。先假设要求一个连通无向图G=(V, E)的最小生成树T,且以其中的一个顶点V1为T的根结点。下面就分别对这两种算法进行介绍。

1、Prim算法-O(n2)——适合稠密图

基本思想
Prim算法构建最小生成树的过程是:先构建一棵只包含根结点V1的树A,然后每次在连接树A结点和图G中树A以外的结点的所有边中,选取一条权重最小的边加入树A,直至树A覆盖图G中的所有结点。

例题:
给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。

给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。

由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。

输入格式
第一行包含两个整数n和m。

接下来m行,每行包含三个整数u,v,w,表示点u和点v之间存在一条权值为w的边。

输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。

数据范围
1≤n≤500,
1≤m≤105,
图中涉及边的边权的绝对值均不超过10000。

输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6

分析:
Prim算法和Dijksra算法的实现十分的相似,均是基于贪心的思想。

将整个点集分为两部分,一部分为S1,表示已确定的最小生成树中的边的端点。另一部分为S2,表示剩下的点。

初始状态可以任选一点S加入点集,距离数组dis记录的是其他点到S的距离,dis[S]=0,再用S点来更新剩下的点到点集S1的最短距离。接下来每次从S2中选择一个到S1距离最近的点将其加入S1,再用该点更新S2中的其他点到S1的距离。重复以上操作n次,将n个点均加入S1中。

题目代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=510,inf=0x3f3f3f3f;
int n,m,g[N][N];
bool st[N];
int prim()
{
    int dis[N];
    memset(dis,0x3f,sizeof dis);
    int res=0;
    for(int i=0;i<n;i++)
    {
        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) res+=dis[t];
        st[t]=true;
        for(int j=1;j<=n;j++) dis[j]=min(dis[j],g[t][j]);
    }
    return res;
}
int main()
{
    memset(g,0x3f,sizeof g);
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    int t=prim();
    if(t==inf) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

2、Kruskal算法-O(mlogm)——适合稀疏图

基本思想
假设现在要求无向连通图G=(V, E)的最小生成树T,Kruskal算法的思想是令T的初始状态为|V|个结点而无边的非连通图,T中的每个顶点自成一个连通分量。接着,每次从图G中所有两个端点落在不同连通分量的边中,选取权重最小的那条,将该边加入T中,如此往复,直至T中所有顶点都在同一个连通分量上。

例题:

给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。

给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。

由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。

输入格式
第一行包含两个整数n和m。

接下来m行,每行包含三个整数u,v,w,表示点u和点v之间存在一条权值为w的边。

输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。

数据范围
1≤n≤105,
1≤m≤2∗105,
图中涉及边的边权的绝对值均不超过1000。

输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6

分析:
Kruskal算法实现步骤:
①、将所有边按权重从小到大排序——O(mlog2m)。
②、从小到大依次枚举每条边,只要加入后图中不会出现环,就将这条边加入到集合中。——O(m)。
第②步具体的可以通过并查集来实现。
由于mlog2m与mlog2n在很多情况下是一个级别的,且Kruscal算法比较好写,因此稀疏图常用Kruskal算法

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=2e5+10,inf=0x3f3f3f3f;

int n,m,p[N];
struct Edge
{
    int u,v,w;
    bool operator <(const Edge &t)
    {
        return w<t.w;
    }
}e[N];

int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(e,e+m);
    
    for(int i=1;i<=n;i++) p[i]=i;
    
    int res=0,cnt=0;
    for(int i=0;i<m;i++)
    {
        int a=e[i].u,b=e[i].v,w=e[i].w;
        
        a=find(a),b=find(b);
        if(a!=b)
        {
            p[a]=b;
            res+=w;
            cnt++;
        }
    }
    
    if(cnt<n-1) return inf;
    return res;
}

int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        e[i]={a,b,c};
    }
    
    int t=kruskal();
    if(t==inf) puts("impossible");
    else cout<<t<<endl;
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值