最小生成树(prim和kruskal)学习笔记

有两个求最小生成树的算法,prim算法和kruskal算法。这两种算法都可以处理边权为负的情况,并且可以处理有负权回路的情况。接下来会分析一下两个算法的区别。

prim算法

这个算法思路主要是不断向最小生成树中添加点,而这个添加的点是距离生成树最近的点。

这个算法主要用在稠密图里面,因为是不断添加点,所以时间复杂度和边没有关系,就算是稠密图有再多的边,只要点不多就没有问题。

实现 : 稠密图我们可以用邻接矩阵去储存图,所以开个g[i][j],表示从i到j的距离,如果是无向图的话,注意赋值的时候要g[i][j]和g[j][i]都来一遍,我们还需要一个dis[N]数组来记录一下每个点到生成树的距离,首先,我们需要对所有的点都遍历一次,所以最外层有个n次的for循环,对于每次操作,我们必须得找到距离最近的点,并且这个点我们不能加入过,所以我们可以再加一个数组记录一下某个点是否被遍历过,现在是怎么找到距离最近的点呢?定义一个minn变量,初始化为无穷大,如果遍历到的这个点的距离小于minn,就更改minn,这样就ok了,如果最后发现idx(记录距离最小的节点)没有发生改变,说明剩下的点距离都是无穷大了,说明出现了孤立点,它的距离没有被更新,这时候说明这个图没有最小生成树,如果idx被更新了,说明找到了距离最小的点,这时候就得更新其他没有加入过的点到生成树的距离了。还有就是初始化的问题了,dis数组初始化为无穷大,g[i][j]也初始成无穷大。

时间复杂度 : o(n*n)

例题

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

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

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

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

输入格式

第一行包含两个整数 nn 和 mm。

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

输出格式

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

数据范围

1≤n≤5001≤n≤500,
1≤m≤1051≤m≤105,
图中涉及边的边权的绝对值均不超过 1000010000。

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
#define endl "\n"
#define lowbit(x) (x&(-x))
const int N = 510;
int g[N][N];
int dis[N];
bool st[N];
int n,m;
void prim(){
    memset(dis, inf, sizeof dis);
    dis[1] = 0;
    int res = 0;
    for(int i = 1; i <= n; i++){
        int idx = 0, minn = inf;
        for(int j = 1; j <= n; j++){
            if(!st[j]&&dis[j]<minn){
                minn = dis[j];
                idx = j;
            }
        }
        if(idx==0){
            cout << "impossible" << endl;
            return;
        }
        res += dis[idx];
        st[idx] = true;
        for(int j = 1; j <= n; j++){
            if(!st[j]&&dis[j]>g[idx][j]) dis[j] = g[idx][j]; 
        } 
    }
    cout << res << endl;
}
void solve(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++) g[i][j] = inf;
    }
    while(m--){
        int a,b,c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(c,g[a][b]);
    }
    prim();
}
signed main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int T=1;
    //cin >> T;
    while(T--) solve();
    return 0;
}

//
//
//
//
//

我在写代码时出现的问题:g[a][b] = g[b][a] = min(c,g[a][b]); 因为有重边,我们要记录最短的一条,所以这样写,而不是g[a][b] = g[b][a] = c;   还有一个是dis[1] = 0; 因为需要起点的位置更新其他的距离,所以如果忘了这个的话,第一次就更新不了idx,然后就输出impossible了。

kruskal算法

这个算法思路主要是不断向最小生成树中添加边

这个算法主要用在稀疏图里面,因为是不断添加边,所以时间复杂度和点没有关系,就算是稀疏图有再多的点,只要边不多就没有问题。

思路:我们用结构体去储存每一条边,然后把这些边按边权从小到大排序,然后添加的时候先挑边权小的边添加,每当判断一个边的时候,需要看这条边是否可以与之前已经构建的生成树合成一个环,如果能合成一个环的话,就舍弃这一条边,判断能否合成一个环可以用并查集来判断,并查集储存这个边的两个顶点,如果这两个顶点的代表元素相同,那么这两个顶点可以合成一个环。好奇怪,同一个代码第一次没过,找了一个小时bug,没找到问题,结果第二次过了???

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
#define endl "\n"
#define lowbit(x) (x&(-x))
const int N = 1e5+10;
int fa[N];
struct E{
    int a,b,c;
    bool operator < (const E& rhs){//通过边长进行排序
        return this->c < rhs.c;
    }
}edg[2*N];
int n,m,res,cnt;
int find(int x){
    if(x==fa[x]) return x;
    else{
        fa[x] = find(fa[x]);
        return fa[x];
    }
}
void ksl(){
    for(int i = 1; i <= m; i++){
       int pa = find(edg[i].a), pb = find(edg[i].b);
       if(pa!=pb){
         res += edg[i].c;
         fa[pa] = pb;
         cnt++; 
       }
    }
    //cout << cnt << endl;
    if(cnt>=n-1) cout << res << endl;
    else cout << "impossible" << endl;
}
void solve(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) fa[i] = i;
    for(int i = 1; i <= m; i++){
        cin >> edg[i].a >> edg[i].b >> edg[i].c;
    }
    sort(edg+1,edg+1+m);
    ksl();
}
signed main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int T=1;
    //cin >> T;
    while(T--) solve();
    return 0;
}

//
//
//
//
//

结构体排序

bool operator < (const E& tmp){
    return this->c < tmp.c;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值