文章链接:prim算法精讲kruskal算法精讲

题目链接:53. 寻宝


最小生成树

最小生成树是所有节点的最小连通子图, 即:以最小的成本(边的权值)将图中所有节点链接到一起。


prim算法

prim三部曲

第一步,选距离生成树最近节点;

第二步,最近节点加入生成树;

第三步,更新非生成树节点到生成树的距离(即更新minDist数组)。

动画演示见B站视频:【最小生成树(Kruskal(克鲁斯卡尔)和Prim(普里姆))算法动画演示】https://www.bilibili.com/video/BV1Eb41177d1?vd_source=65a224d3f97ae5a1002c0964faf8a876


#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {
    int v, e;cin >> v >> e;
    int x, y, k;
    vector<vector<int>> grid(v + 1, vector<int>(v + 1, 10001)); // 默认最大值
    while (e--) {
        cin >> x >> y >> k;
        grid[x][y] = k;
        grid[y][x] = k; // 无向图,两个方向都要加上
    }
    vector<int> minDist(v + 1, 10001); // 所有节点到最小生成树的最小距离
    vector<bool> isInTree(v + 1, false); // 判断节点是否在树里
    
    for (int i = 1; i < v; i++) { // n - 1 条边
        // 第一步,选取距离最小生成树最近的节点
        int cur = -1; // 未加入最小生成树
        int minval = INT_MAX;
        for (int j = 1; j <= v; j++) { // 遍历所有点找出距离最小生成树最近的节点
            if (!isInTree[j] && minDist[j] < minval) {
                // 选取最小生成树节点的条件:
                // 1.不在最小生成树里;
                // 2.距离最小生成树最近的节点
                minval = minDist[j]; //  选小的
                cur = j; 
            }
        }
        
        // 第二步,将最近的节点加入生成树
        isInTree[cur] = true; 
        
        // 第三步,更新minDist数组
        for (int j = 1; j <= v; j++) { // 遍历所有点
            if (!isInTree[j] && grid[cur][j] < minDist[j]){
            //  1.节点是非生成树里的节点
            //  2.与cur相连的某节点的权值比该某节点距离最小生成树的距离小
                minDist[j] = grid[cur][j];
            }
            
        }
    }
    int result = 0;
    for (int i = 2; i <= v; i++) { // 不计算第一个点,因为统计的是边的权值
        result += minDist[i];
    }
    cout << result << '\n';
}


kruskal算法

与prim算法的区别:

prim 算法是维护节点的集合,而 Kruskal 是维护边的集合

思路:

第一步:边的权值排序,因为要优先选最小的边加入到生成树里;

第二步:遍历排序后的边:

  • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环;
  • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合。

判断是否成环:用并查集

动画演示见B站视频:【最小生成树(Kruskal(克鲁斯卡尔)和Prim(普里姆))算法动画演示】https://www.bilibili.com/video/BV1Eb41177d1?vd_source=65a224d3f97ae5a1002c0964faf8a876


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Edge {
    int l, r, val;
    // l, r 为边两边的节点,val为边的数值
};

int n = 10002;
vector<int> father(n, -1);

void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
    }
}

int find(int u) {
    return u == father[u] ? u : father[u] = find(father[u]); // 路径压缩
}

void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根
    if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
    father[v] = u;
}

int main() {
    int v, e;cin >> v >> e;
    int x, y, k;
    vector<Edge> edges;
    int result = 0;
    while (e--) {
        cin >> x >> y >> k;
        edges.push_back({x, y, k});
    }
    
    sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b){
        return a.val < b.val;
    }); //  lambda 表达式
    
    init();
    
    for (Edge edge: edges) {
        int x = find(edge.l);
        int y = find(edge.r);
        
        if (x != y) { // 不成环
            result += edge.val;
            join(x, y); // 加入最小生成树中(集合)
        }
    }
    cout << result << '\n';
    return 0;
}


总结:

Kruskal 与 prim 的关键区别在于,prim维护的是节点的集合,而 Kruskal 维护的是边的集合。 如果 一个图中,节点多,但边相对较少,那么使用Kruskal 更优。

所以在 稀疏图中,用Kruskal更优。 在稠密图中,用prim算法更优。

Prim 算法 时间复杂度为 O(n^2),其中 n 为节点数量,它的运行效率和图中边树无关,适用稠密图。

Kruskal算法 时间复杂度 为 nlogn,其中n 为边的数量,适用稀疏图。