【学习】最小生成树-Prim

发布时间 2023-07-30 18:48:08作者: _Kiichi

最小生成树(Prim)学习笔记

展开目录

Before

为了做个挖水井去学了 Prim 虽然根本不是算法的锅

前置知识是 \(dijkstra\), 有一定相似度

Prim

\(Kruskal\) 是一种适用于稀疏图的算法,而对于稠密图,也有其适用的算法——Prim。

核心思想

Prim 主要处理的是点,和 \(Kruskal\) 的针对边来处理不同。

为了方便表述,使用了“起点”和“终点”来形容,实际上所有边都是无向的。

\(dijkstra\) 类似,从任意一个点出发,选取这个点连接的所有边中,权值最小且终点未标记过的一条,标记这条边通向的点。再以新的点为起点,重复以上操作直到所有点都被标记。

这里除了选择的点是“距离上一个被标记的点距离最小”之外,与 \(dijkstra\) 的思想几乎相同。

时间复杂度 \(O(n^2)\), 但是在密集图中性能比 \(Kruskal\) 好。

堆优化

类似 \(dijkstra\) 的堆优化,Prim 也是有堆优化的。

在稀疏图中的时间复杂度是 \(O(m\log n)\), 但是在密集图中会被卡回 \(O(n^2)\), 甚至还不如不优化。

例题

挖水井

题面:P1550

一开始用 \(Kruskal\) 死活只有 \(10pts\), 后来换了 Prim 也只能拿 \(50pts\), 后来发现是思路错了,于是去看了个题解:

因为可以挖不止一口井,所以可以把挖井的花费看作是从水源往回运水的花费。新增点 \(n + 1\) 作为水源,把从点 \(i\) 挖井的花费看作 \(i\)\(n + 1\) 之间边的边权,然后跑一个最短路的板子,大功告成。

代码是我用之前写的 dij 板子改的,足以看出 Prim 跟 dij 真的没什么大区别。

展开代码
#include <bits/stdc++.h>
#define ll long long
#define MyWife Cristallo
using namespace std;
const int N = 1e5 + 5;
int n, m, s, ans;
int head[N], to[2 * N], weal[2 * N], dis[N], nxt[2 * N], tot;
bool vis[N];
void add(int u, int v, int w) {
    to[++tot] = v;
    weal[tot] = w;
    nxt[tot] = head[u];
    head[u] = tot;
}
struct node {
    int dis, pos;
};
bool operator < (const node &x, const node &y) {
    return x.dis > y.dis;
}
priority_queue<node> q;
inline void prim() {
    int cnt = 0;
    dis[s] = 0;
    q.push((node){0, s});
    while(!q.empty()) {
        node tem = q.top();
        q.pop();
        int x = tem.pos, d = tem.dis;
        if(vis[x]) continue;
        vis[x] = 1, ++cnt;
        ans += d;
        for(int i = head[x]; i; i = nxt[i]) {
            int y = to[i], w = weal[i];
            if(dis[y] > w) {
                dis[y] = w;
                if(!vis[y]) q.push((node){dis[y], y});
            }
        }
    }
    if(cnt < n) ans = 0x7fffffff;
}
int main() {
    scanf("%d", &n);
    ++n;
    int w;
    for(int i = 1; i < n; ++i) {
        scanf("%d", &w);
        add(n, i, w);
        add(i, n, w);
    }
    for(int i = 1; i < n; ++i) for(int j = 1; j < n; ++j) {scanf("%d", &w); add(i, j, w);}
    for(int i = 1; i <= n; ++i) dis[i] = 0x7fffffff;
    s = 1;
    prim();
    printf("%d", ans);
    return 0;
}