最小生成树(MST)

最小生成树(Minimum Spanning Tree, MST)是图论中的一个重要概念,指在一个连通加权无向图中,找到一棵包含所有顶点的生成树,且这棵树所有边的权值之和最小。最小生成树在实际中有广泛应用,如网络设计、电路布线、交通规划等领域。

一、最小生成树的基本性质

  1. 定义:对于连通加权无向图G=(V,E),最小生成树T是G的一个子图,满足:
    • 包含G的所有顶点
    • 是一棵树(无环且连通)
    • 所有边的权值之和最小
  2. 性质
    环切分性质:图中任意环,删除权值最大的边后,剩余边仍构成生成树
    边选择性质:若边是连接两个子树的最小权边,则该边必属于某棵MST
    唯一性:当所有边权值不相同时,MST唯一;否则可能存在多个MST

二、经典算法

1. Prim算法(普里姆算法)

基本思想:从顶点出发,逐步扩展生成树,每次选择连接生成树与非生成树顶点的最小权边。

算法步骤

  1. 任选一个起始顶点加入生成树
  2. 在所有连接生成树与非生成树顶点的边中,选择权值最小的边加入生成树
  3. 将新顶点加入生成树集合
  4. 重复步骤2-3,直到所有顶点都加入生成树

时间复杂度

  • 邻接矩阵实现:O(V²)
  • 二叉堆优化:O(E log V)
  • 斐波那契堆优化:O(E + V log V)

C++实现

int prim(vector<vector<pair<int, int>>>& graph, int start) {
    int n = graph.size();
    vector<int> dist(n, INT_MAX);
    vector<bool> visited(n, false);
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;

    dist[start] = 0;
    pq.push({0, start});
    int total = 0;

    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (visited[u]) continue;
        visited[u] = true;
        total += d;

        for (auto [v, w] : graph[u]) {
            if (!visited[v] && w < dist[v]) {
                dist[v] = w;
                pq.push({w, v});
            }
        }
    }
    return total;
}

2. Kruskal算法(克鲁斯卡尔算法)

基本思想:按边权值从小到大排序,依次选择不形成环的边加入生成树。

算法步骤

  1. 将所有边按权值从小到大排序
  2. 初始化并查集数据结构
  3. 依次检查每条边,如果边的两个端点不在同一集合,则加入生成树并合并集合
  4. 重复步骤3直到生成树包含V-1条边

时间复杂度

  • 排序主导:O(E log E)
  • 并查集操作:近似O(α(V))
  • 总复杂度:O(E log E)

C++实现

struct DSU {
    vector<int> parent;
    DSU(int n) : parent(n) { iota(parent.begin(), parent.end(), 0); }
    int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
    bool unite(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return false;
        parent[y] = x;
        return true;
    }
};

int kruskal(vector<vector<int>>& edges, int n) {
    sort(edges.begin(), edges.end(), [](auto& a, auto& b) { return a[2] < b[2]; });
    DSU dsu(n);
    int total = 0, count = 0;

    for (auto& e : edges) {
        if (dsu.unite(e[0], e[1])) {
            total += e[2];
            if (++count == n - 1) break;
        }
    }
    return count == n - 1 ? total : -1;
}

三、算法比较与选择

特性Prim算法Kruskal算法
思想顶点扩展边选择
数据结构优先队列并查集+排序
时间复杂度O(E log V)O(E log E)
适用场景稠密图稀疏图
实现难度中等较简单

选择建议

  • 当图较稠密(E≈V²)时,Prim算法更高效
  • 当图较稀疏(E≈V)时,Kruskal算法更合适
  • 需要动态维护生成树时,Prim算法更方便

四、应用实例

  1. 网络设计:在多个城市间建立通信网络,最小化布线成本
  2. 电路设计:连接多个元件,最小化导线总长度
  3. 聚类分析:基于相似度构建层次聚类结构
  4. 图像分割:将图像像素连接成区域
滚动至顶部