算法学习笔记(29)——Prim算法(最小生成树)

Prim 算法

Prim算法用于求最小生成树问题,与Dijkstra算法非常相似。根据图的疏密程度,分为朴素Prim算法(稠密图,\(O(n^2)\))、堆优化Prim算法(稀疏图,\(O(m\log n)\))。但对于稀疏图,Kruskal算法更好写,思路更加清晰简单,所以一般不使用堆优化Prim。本文我们仅学习朴素Prim算法。

用集合 \(S\) 表示当前已经在最小生成树中的所有点,初始时 \(S\) 是空集。(代码实现中通常利用一个bool数组st[]做标记,若为true则表示在集合中,否则不在集合中)

算法思路
首先将所有点到最小生成树集合的距离初始化为正无穷,即:

\[dist[i] \Longleftarrow +\infty \]

接下来进行 \(n\) 次迭代,每次:

  1. 找到集合外距离集合最近的点 \(t\)(初始状态每个点到集合的距离都是正无穷,所以随便选择一个)
  2. \(t\) 更新其他点到集合的距离
  3. \(t\) 加入到集合中,即 st[t] = true

最后得到的最小生成树就是每次选中的点 \(t\)

相较于Dijkstra算法一开始就选中一个点作为源点,只需要循环 \(n-1\) 次,而Prim算法需要循环 \(n\) 次。

最小生成树问题中不涉及环路问题,边权是正是负没有影响。

题目链接:AcWing 858. Prim算法求最小生成树

#include <iostream>
#include <cstring>

using namespace std;

const int N = 510, M = 1e5 + 10;
const int 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;
    
    // n次循环
    for (int i = 0; i < n; i ++ ) {
        // 首先找到不在集合中的距离最近的点
        int t = -1; // 这里的-1表示第一次循环,随便找一个点加入即可
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        
        // 如果不是第一轮循环且找出的点距离是正无穷,则不存在最小生成树
        if (i && dist[t] == INF) return INF;
        // 如果不是第一轮循环,累计新加入的点到连通块的距离
        if (i) res += dist[t];
        // 标记当前点加入最小生成树集合
        st[t] = true;
                
        // 用t更新其他点到集合的距离
        for (int j = 1; j <= n; j ++ )
            // 注意此处与dijkstra算法不同,更新的是到连通块的距离g[t][j],而不是到源点的距离dist[t]+g[t][j]
            dist[j] = min(dist[j], g[t][j]);
    }
    return res;
}

int main()
{
    // 初始化邻接矩阵,没有任何边,所有边初始化为正无穷
    memset(g, 0x3f, sizeof g);
    
    cin >> n >> m;
    while (m -- ) {
        int x, y, z;
        cin >> x >> y >> z;
        g[x][y] = g[y][x] = min(g[x][y], z); // 无向图添加双向边
    }
    
    int t = prim(); // 利用t存储,避免在if...else...判断中执行两次算法
    
    if (t == INF) puts("impossible");
    else cout << t << endl;
    
    return 0;
}

另外要注意先累加res,也就是先生成答案里的这条边,再去做更新,因为更新的时候是可能把dist[t]更新掉的。

posted @ 2022-12-10 09:32  S!no  阅读(147)  评论(0编辑  收藏  举报