最小生成树算法详解

算法思想

最小生成树(MST)问题的核心思想基于一个贪心策略:我们总是选择当前可用的最小权值边,同时保证不形成环路。这个简单而强大的思想支撑了两个经典算法。

Kruskal 算法

基本思路

  1. 将所有边按权重从小到大排序
  2. 依次考察每条边,如果这条边连接的两个顶点不在同一个连通分量中,则选择这条边加入最小生成树
  3. 重复步骤2,直到选择了n-1条边(或所有顶点都在同一连通分量中)

核心思想解析

Kruskal算法的贪心策略是"全局贪心"---考虑整个图中权值最小的边。算法维护一个森林,初始时每个顶点自成一棵树。随着边的加入,森林中的树逐渐合并,最终形成一棵包含所有顶点的最小生成树。

关键点

  • 使用并查集(Union-Find)来高效判断两个顶点是否在同一连通分量
  • 对边排序是算法效率的主要瓶颈
  • 特别适合稀疏图(边数远小于顶点数的平方)

Prim 算法

基本思路

  1. 从任意顶点开始,将其加入最小生成树
  2. 在所有连接"已选顶点集合"和"未选顶点集合"的边中,选择权值最小的边
  3. 将这条边以及对应的未选顶点加入最小生成树
  4. 重复步骤2和3,直到所有顶点都加入最小生成树

核心思想解析

Prim算法的贪心策略是"局部贪心"---考虑当前树到其余顶点的最小边。算法维护一个不断生长的树,每次选择一个最近的顶点加入。

关键点

  • 使用优先队列维护未加入顶点到当前树的最小距离
  • 每次选择距离当前树最近的顶点
  • 特别适合稠密图(边数接近顶点数的平方)

算法比较

  • Kruskal:O(E log E),适合稀疏图
  • Prim:O(E log V),适合稠密图
  • 两种算法在正确实现的情况下都能得到最小生成树(如有多个,可能不同)

代码实现

Kruskal 算法实现

cpp 复制代码
struct Edge {
    int u, v, weight;
    bool operator<(const Edge &other) const {
        return weight < other.weight;
    }
};

class UnionFind {
private:
    vector<int> parent, rank;
public:
    UnionFind(int n) {
        parent.resize(n + 1);
        rank.resize(n + 1, 0);
        for (int i = 1; i <= n; i++) parent[i] = i;
    }
    
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    
    bool unite(int x, int y) {
        int rootX = find(x), rootY = find(y);
        if (rootX == rootY) return false;
        
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else {
            parent[rootY] = rootX;
            if (rank[rootX] == rank[rootY]) rank[rootX]++;
        }
        return true;
    }
};

pair<int, vector<Edge>> kruskal(int n, vector<Edge> &edges) {
    // 按权重排序所有边
    sort(edges.begin(), edges.end());
    
    UnionFind uf(n);
    int total_weight = 0;
    vector<Edge> mst;
    
    for (const Edge &e : edges) {
        if (uf.unite(e.u, e.v)) {
            total_weight += e.weight;
            mst.push_back(e);
            if (mst.size() == n - 1) break;  // 已经有n-1条边,MST构建完成
        }
    }
    
    return {total_weight, mst};
}

Prim 算法实现

cpp 复制代码
typedef pair<int, int> pii;  // (weight, vertex)

pair<int, vector<pair<int, int>>> prim(vector<vector<pii>> &graph, int n) {
    vector<bool> visited(n + 1, false);
    priority_queue<pii, vector<pii>, greater<pii>> pq;
    
    int total_weight = 0;
    vector<pair<int, int>> mst_edges;  // (u, v) 表示边
    
    // 从顶点1开始
    pq.push({0, 1});  // {weight, vertex}
    vector<int> parent(n + 1, -1);  // 用于记录MST中每个点的父节点
    
    while (!pq.empty()) {
        auto [weight, u] = pq.top();
        pq.pop();
        
        if (visited[u]) continue;
        visited[u] = true;
        
        // 将边加入MST(除了起始点)
        if (parent[u] != -1) {
            total_weight += weight;
            mst_edges.push_back({parent[u], u});
        }
        
        // 检查所有相邻边
        for (auto &[v, w] : graph[u]) {
            if (!visited[v]) {
                pq.push({w, v});
                parent[v] = u;
            }
        }
    }
    
    return {total_weight, mst_edges};
}
相关推荐
倒头就睡的小比特2 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!2 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼2 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光2 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark2 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her12 天前
并查集-听课笔记
笔记·算法·并查集
码流子2 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven2 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark2 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218612 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法