最小生成树算法终极指南:从Kruskal到Prim的优化之路

最小生成树算法终极指南:从Kruskal到Prim的优化之路

【免费下载链接】OI-wiki :star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法) 【免费下载链接】OI-wiki 项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki

你是否在解决图论问题时被复杂的生成树算法困扰?是否想知道如何在稠密图和稀疏图中选择最优实现?本文将带你一文掌握最小生成树(Minimum Spanning Tree, MST)的两大核心算法,通过图解和实战代码,从基础实现到高级优化,让你彻底搞懂Kruskal与Prim算法的适用场景与性能瓶颈。

Kruskal算法:基于并查集的边贪心策略

核心思想与步骤

Kruskal算法通过排序边权值并使用并查集(Disjoint Set Union, DSU) 维护连通性,实现了对稀疏图的高效处理。其核心步骤如下:

  1. 按边权值升序排序所有边
  2. 初始化并查集,每个节点自成集合
  3. 依次遍历边,若连接不同集合则加入生成树并合并集合
  4. 直至生成树包含n-1条边或遍历完所有边
// 代码来源:docs/graph/code/kruskal.cpp
#include <algorithm>
#include <vector>
using namespace std;

struct Edge { int u, v, w; };
vector<Edge> edges;
vector<int> parent, rank_;

bool compare(const Edge& a, const Edge& b) { return a.w < b.w; }

int find(int x) {
    if (parent[x] != x) parent[x] = find(parent[x]); // 路径压缩
    return parent[x];
}

void unite(int x, int y) {
    x = find(x), y = find(y);
    if (x == y) return;
    if (rank_[x] < rank_[y]) parent[x] = y; // 按秩合并
    else {
        parent[y] = x;
        if (rank_[x] == rank_[y]) rank_[x]++;
    }
}

int kruskal(int n) {
    sort(edges.begin(), edges.end(), compare);
    parent.resize(n+1); rank_.resize(n+1, 0);
    for (int i = 1; i <= n; i++) parent[i] = i;
    
    int res = 0, cnt = 0;
    for (auto& e : edges) {
        if (find(e.u) != find(e.v)) {
            unite(e.u, e.v);
            res += e.w;
            if (++cnt == n-1) break;
        }
    }
    return cnt == n-1 ? res : -1; // 判断是否连通
}

优化关键:并查集的路径压缩与按秩合并

Kruskal算法的效率瓶颈在于边排序(O(m log m))和并查集操作。通过路径压缩(将查询路径扁平化)和按秩合并(小集合合并入大集合),可将并查集操作优化至近乎常数时间。优化前后的性能对比:

操作普通并查集优化后并查集
单次findO(log n)O(α(n))
单次unionO(log n)O(α(n))
整体时间复杂度O(m log m + m log n)O(m log m + m α(n))

α(n)为反阿克曼函数,实际应用中可视为常数(n<10^60时α(n)≤5)

Prim算法:基于顶点的贪心扩张策略

核心思想与步骤

Prim算法通过维护已选顶点集候选边集,逐步扩张生成树,适用于稠密图。标准实现使用邻接矩阵(O(n²)),优化版本采用优先队列(O(m log n)):

  1. 任选起始顶点加入生成树
  2. 将与已选顶点相连的边加入优先队列
  3. 提取最小权值边,若连接新顶点则加入生成树
  4. 重复直至生成树包含n-1条边
// 代码来源:docs/graph/code/prim.cpp
#include <queue>
#include <vector>
using namespace std;

const int INF = 1e9;
vector<vector<pair<int, int>>> adj; // adj[u] = {v, w}
vector<int> dist; // 到已选集合的最小距离
vector<bool> in_tree;

int prim(int n) {
    dist.assign(n+1, INF);
    in_tree.assign(n+1, false);
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
    
    dist[1] = 0;
    pq.emplace(0, 1);
    int res = 0, cnt = 0;
    
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (in_tree[u]) continue;
        in_tree[u] = true;
        res += d;
        if (++cnt == n) break;
        
        for (auto [v, w] : adj[u]) {
            if (!in_tree[v] && w < dist[v]) {
                dist[v] = w;
                pq.emplace(w, v);
            }
        }
    }
    return cnt == n ? res : -1;
}

高级优化:斐波那契堆与邻接表

在稠密图中,Prim算法可通过斐波那契堆将优先队列操作优化至O(m + n log n),但实现复杂度较高。实际应用中,对于顶点数较少的场景(n<1000),可使用索引堆(Index Heap)优化,避免重复元素入队:

// 索引堆优化示意(仅核心部分)
for (auto [v, w] : adj[u]) {
    if (!in_tree[v] && w < dist[v]) {
        dist[v] = w;
        if (heap.contains(v)) heap.decrease_key(v, w);
        else heap.insert(v, w);
    }
}

算法对比与场景选择

指标Kruskal算法Prim算法(优先队列版)
时间复杂度O(m log m)O(m log n)
空间复杂度O(m + n)(存储边和并查集)O(n + m)(邻接表和堆)
适用图类型稀疏图(m≈n)稠密图(m≈n²)
实现难度较简单(并查集为核心)较复杂(堆操作需小心)
并行化潜力高(边排序可并行)低(依赖顶点扩张顺序)

实际开发建议:当m < n log n时选择Kruskal,否则选择Prim;竞赛中推荐掌握两种实现,根据题目数据范围切换。

实战应用与常见陷阱

典型例题解析

在docs/graph/examples/mst_problem.md中收录了经典例题,如「有线电视网建设」问题:

某地区有n个村庄,要实现村村通电视,已知每两个村庄间的电缆铺设成本,求最小总铺设成本。

该问题可直接套用Kruskal算法,关键在于:

  • 处理重边:排序时自动保留最小边
  • 处理非连通图:判断生成树边数是否为n-1
  • 输出方案:记录选中的边而非仅计算权值和

常见错误与调试技巧

  1. 并查集初始化错误:忘记初始化parent数组或rank数组

    // 错误示例
    for (int i = 0; i < n; i++) parent[i] = i; // 顶点编号从1开始时错误
    // 正确示例
    for (int i = 1; i <= n; i++) parent[i] = i;
    
  2. Prim算法起点选择:孤立点导致无法生成树,需在代码中返回-1或其他标记值

  3. 边权值溢出:使用32位整数时需注意(如1e5条边,每条边权1e9,总和可能超过2e14,需用long long)

总结与进阶资源

最小生成树算法是图论的基础工具,掌握其优化技巧对提升程序性能至关重要。进一步学习建议:

本文代码和图解均来自OI-Wiki项目,完整内容可查阅官方文档。建议结合在线评测系统进行实战训练,巩固算法理解。

收藏本文,下次遇到MST问题时即可快速查阅;点赞支持让更多人看到这份优化指南!

【免费下载链接】OI-wiki :star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法) 【免费下载链接】OI-wiki 项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值