通关搜索和图论 day_16 -- Prim & Kruskal

Prim

先初始化距离,全部是正无穷,然后 n 次迭代,每次迭代执行:

1.找到 不在集合当中的 距离最小的点(这里的集合是 当前的生成树

2.用这个点更新其他点到集合的距离

举例,我们有如下图

image-20230111092121285

第一步,让所有点到集合的距离变 +无穷

然后因为所有点的距离都是正无穷,所以我们随便挑一个点作为第一个点,把他加入集合

其他点到集合的距离,实际就是看看其他点能不能连一条线到集合内部

那么某个点到集合的距离就定义成,这个点到集合任意一条边最小的距离,如果这个点没有一条边连到集合,那么距离就被定义成正无穷

那么现在 2号点到集合的距离被更新成1

3号点到集合的距离被更新成2

4号点到集合的距离被更新成3

第二次迭代就找集合外 距离最近的点也就是 2 号点

重复上述过程,直到所有点进入集合

生成树是什么呢?就是每次选择的那个点指向集合的边就是我们生成树的一条边

模板

int n;      // n表示点数
int g[N][N];        // 邻接矩阵,存储所有边
int dist[N];        // 存储其他点到当前最小生成树的距离
bool st[N];     // 存储每个点是否已经在生成树中


// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{
    memset(dist, 0x3f, sizeof dist);

    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 || dist[t] > dist[j]))
                t = j;

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

例题

858. Prim算法求最小生成树 - AcWing题库

给定一个 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
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;

const int N = 510,INF = 0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];
bool st[N];

int prim()
{
    //  把所有距离初始化成正无穷
    memset(dist,0x3f,sizeof dist);
    int res = 0;    //res 存的是最小生成树里所有边的长度之和
    //  n 次迭代
    for (int i = 0; i < n; i++) {
        int t = -1;
        for (int j = 1; j <= n ; j++) {
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
            {
                t = j;
            }
        }

        if (i && dist[t] == INF) return INF;    // 不是第一个点,到集合的距离还是正无穷说明无法生成最小生成树
        if (i) res += dist[t];
        for (int j = 1; j <= n ; j++) {
            dist[j] = min(dist[j],g[t][j]);
        }

        st[t] = true;
    }
    return res;
}

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    memset(g,0x3f,sizeof g);
    while(m--)
    {
        int a,b,c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b],c);
    }

    int t = prim();
    if (t == INF) cout << "impossible" << endl;
    else cout << t << endl;
}

Kruskal

第一步 先将所有边 按照权重从小到大排序

第二步 枚举每条边 ab 权重为 c

如果 ab 不连通,将这条边加入集合中

模板

int n, m;       // n是点数,m是边数
int p[N];       // 并查集的父节点数组

struct Edge     // 存储边
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // 并查集核心操作
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + 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 = edges[i].a, b = edges[i].b, w = edges[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;
}

例题

859. Kruskal算法求最小生成树 - AcWing题库

给定一个 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
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;

const int N = 2000010;
int n,m;
int p[N];

struct Edge{
    int a,b,w;

    bool operator< (const Edge &W)const{
        return w < W.w;
    }
}edges[N];

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


int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);

    cin >> n >> m;

    for (int i = 0; i < m; ++i) {
        int a,b,w;
        cin >> a >> b >> w;
        edges[i] = {a,b,w};
    }

    sort(edges,edges + 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 = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
    
    if (cnt < n - 1)
    {
        cout << "impossible" << endl;
    }else{
        cout << res << endl;
    }
}

posted @ 2023-01-13 11:18  ShibuyaKanon  阅读(24)  评论(0)    收藏  举报