【每日算法】Day 1-5:最小生成树终极对决——Prim与Kruskal算法详解(C++实现)

攻克图论核心领域!详解最小生成树两大经典算法——Prim与Kruskal,从原理剖析到代码实战,彻底掌握连通图的最优解生成方法。

一、最小生成树(MST)核心概念

最小生成树是连通加权无向图中边权和最小的生成树,具有以下性质:

  1. 包含所有顶点且无环

  2. 任意两顶点间有且仅有一条路径

  3. 边数 = 顶点数 - 1

应用场景:

  • 城市间光纤网络铺设

  • 电路板布线优化

  • 物流中心选址规划


二、Prim算法详解

算法思想(贪心策略)

从任意顶点出发,逐步扩展生成树,每次选择连接树与非树节点的最小权边对应的顶点加入生成树。

代码实现(邻接表 + 优先队列优化)

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

typedef pair<int, int> pii; // <权重, 目标顶点>

int primMST(vector<vector<pii>>& adj) {
    int V = adj.size();
    vector<bool> inMST(V, false);
    priority_queue<pii, vector<pii>, greater<pii>> pq;
    int totalWeight = 0;

    pq.push({0, 0}); // 从顶点0开始

    while (!pq.empty()) {
        auto [weight, u] = pq.top();
        pq.pop();

        if (inMST[u]) continue;
        inMST[u] = true;
        totalWeight += weight;

        for (auto& edge : adj[u]) {
            int v = edge.second, w = edge.first;
            if (!inMST[v]) {
                pq.push({w, v});
            }
        }
    }
    return totalWeight;
}

// 测试用例
int main() {
    vector<vector<pii>> graph = {
        {{4,1}, {8,2}},        // 顶点0
        {{4,0}, {8,2}, {11,3}},// 顶点1
        {{8,0}, {2,3}, {7,4}}, // 顶点2
        {{11,1}, {2,2}, {6,5}},// 顶点3
        {{7,2}, {9,5}},        // 顶点4
        {{6,3}, {9,4}}         // 顶点5
    };
    cout << "最小生成树总权重:" << primMST(graph);
    return 0;
}

三、Kruskal算法详解

算法思想(并查集应用)

按边权升序选择边,若该边连接的两个顶点不在同一连通分量,则加入生成树,避免形成环。

代码实现(并查集优化)

#include <algorithm>
struct Edge {
    int src, dest, weight;
    bool operator<(Edge const& other) {
        return weight < other.weight;
    }
};

class UnionFind {
    vector<int> parent;
public:
    UnionFind(int n) : parent(n) {
        for(int i=0; i<n; ++i) parent[i] = i;
    }
    int find(int x) {
        return parent[x] == x ? x : parent[x] = find(parent[x]);
    }
    void unite(int x, int y) {
        parent[find(x)] = find(y);
    }
};

int kruskalMST(vector<Edge>& edges, int V) {
    sort(edges.begin(), edges.end());
    UnionFind uf(V);
    int totalWeight = 0, edgeCount = 0;

    for (Edge& e : edges) {
        if (uf.find(e.src) != uf.find(e.dest)) {
            totalWeight += e.weight;
            uf.unite(e.src, e.dest);
            if (++edgeCount == V-1) break;
        }
    }
    return totalWeight;
}

// 测试用例
int main() {
    vector<Edge> edges = {
        {0,1,4}, {0,2,8}, {1,2,8}, {1,3,11},
        {2,3,2}, {2,4,7}, {3,4,9}, {3,5,6}, {4,5,9}
    };
    cout << "最小生成树总权重:" << kruskalMST(edges, 6);
    return 0;
}

四、算法对比与选型指南

特性Prim算法Kruskal算法
时间复杂度O(E + V log V) 堆优化O(E log E) 排序耗时
空间复杂度O(V + E)O(E)
适用图类型稠密图(邻接矩阵)稀疏图(边列表)
数据结构优先队列并查集 + 排序
选择策略顶点扩展边选择
是否需要连通图必须连通可处理非连通图(生成森林)

五、大厂真题实战

真题1:最低成本连通城市(某大厂2024笔试)

题目描述:
给定N个城市之间的道路成本,求使所有城市连通的最低成本(若无法连通返回-1)

解题思路:

  • Kruskal算法天然适合处理边列表形式输入

  • 最终检查生成树边数是否为N-1

int minimumCost(int N, vector<vector<int>>& connections) {
    sort(connections.begin(), connections.end(), 
        [](auto& a, auto& b){return a[2] < b[2];});
    UnionFind uf(N+1); // 城市编号从1开始
    int cost = 0, count = 0;
    for(auto& conn : connections) {
        if(uf.find(conn[0]) != uf.find(conn[1])) {
            uf.unite(conn[0], conn[1]);
            cost += conn[2];
            if(++count == N-1) break;
        }
    }
    return count == N-1 ? cost : -1;
}
真题2:关键连接边判断(某大厂2023面试)

问题描述:
找出图中所有存在于所有最小生成树中的边

解题思路:

  1. 计算原图MST的总权重W

  2. 对每条边e,检查:

    • e必须在某些MST中(权重 ≤ 所在环的最大边)

    • 删除e后MST权重 > W 或无法形成生成树

六、常见误区与注意事项

  1. 负权边处理:两种算法均可处理负权边(MST允许负权)

  2. 图连通性检查:Prim需手动处理,Kruskal自动检测

  3. 边权相等处理:可能存在多个MST(需题目明确要求)

  4. 顶点编号:注意是否从0或1开始计数

  5. 大规模数据优化

    • Prim使用斐波那契堆可优化至O(E + V log V)

    • Kruskal使用计数排序(当边权范围较小时)


七、总结与扩展

核心要点:

  • Prim适合顶点操作,Kruskal适合边操作

  • 并查集的路径压缩优化是关键性能保障

  • 两种算法在不同场景下的效率反转点(E ≈ V²时Prim更优)

扩展思考:

  1. 如何动态维护一个随时增减边的最小生成树?

  2. 当图中存在相同权重的边时,如何统计MST的数量?

  3. 如何利用MST解决旅行商问题(TSP)的近似解?


LeetCode真题练习:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值