当前位置:首页 > C++目录 > 正文内容

最小生成树—Kruskal(克鲁斯卡尔)算法

亿万年的星光2个月前 (12-20)C++目录224

一、算法描述

在一个连通加权无向图中,找到一棵最小生成树。即,找到连接所有顶点的、权值总和最小的树,且树中不包含任何环。


二、核心思想

  1. 贪心策略:每次从未选择的边中,选取一条权值最小的边。

  2. 避免环路:如果加入这条边会导致生成树中形成环,则舍弃它。

  3. 集合管理:使用并查集数据结构来高效地判断两个顶点是否已经连通(即加入边是否会形成环)。


三、图解过程

假设我们有以下连通图 G,目标是找到它的最小生成树(MST)。

步骤 0:初始化

  • 将图中所有边按权值从小到大排序

  • 初始化一个空的边集 MST,用于存放最小生成树的边。

  • 初始化并查集,让每个顶点自成一個集合。

排序后的边列表:(A,D):5, (C,E):5, (D,F):6, (A,B):7, (B,E):7, (B,C):8, (E,F):8, (B,D):9, ...

当前 MST: { }并查集状态: {A}, {B}, {C}, {D}, {E}, {F}

步骤 1:选择最小边 (A,D):5

  • 检查:顶点 A 和 D 是否在同一集合?否(不会形成环)。

  • 将边 (A,D) 加入 MST

  • 在并查集中合并集合 {A} 和 {D}


当前 MST: { (A,D):5 }并查集状态: {A, D}, {B}, {C}, {E}, {F}


步骤 2:选择下一条最小边 (C,E):5

  • 检查:顶点 C 和 E 是否在同一集合?否(不会形成环)。

  • 将边 (C,E) 加入 MST

  • 合并集合 {C} 和 {E}


当前 MST: { (A,D):5, (C,E):5 }并查集状态: {A, D}, {B}, {C, E}, {F}


步骤 3:选择边 (D,F):6

  • 检查:顶点 D 和 F 是否在同一集合?否(D在{A,D},F在{F})。

  • 将边 (D,F) 加入 MST

  • 合并集合 {A, D} 和 {F}

当前 MST: { (A,D):5, (C,E):5, (D,F):6 }并查集状态: {A, D, F}, {B}, {C, E}


步骤 4:选择边 (A,B):7

  • 检查:顶点 A 和 B 是否在同一集合?否(A在{A,D,F},B在{B})。

  • 将边 (A,B) 加入 MST

  • 合并集合 {A, D, F} 和 {B}


当前 MST: { (A,D):5, (C,E):5, (D,F):6, (A,B):7 }并查集状态: {A, B, D, F}, {C, E}


步骤 5:选择边 (B,E):7

  • 关键检查:顶点 B 和 E 是否在同一集合?

    • B 在 {A, B, D, F}

    • E 在 {C, E}

    • 不在同一集合(不会形成环)。

  • 将边 (B,E) 加入 MST

  • 合并两个大集合 {A, B, D, F} 和 {C, E}此时所有顶点都在同一个集合中

当前 MST: { (A,D):5, (C,E):5, (D,F):6, (A,B):7, (B,E):7 }并查集状态: {A, B, C, D, E, F}


算法终止

  • 终止条件MST 中的边数 = 顶点数 - 1 = 5。我们已经找到了最小生成树。

  • 后续的边 (B,C):8(E,F):8(B,D):9 等不再被考虑,因为加入它们都会连接已经连通的顶点,从而形成环。

最终的最小生成树(MST) 如上图红色边所示。总权值 = 5 + 5 + 6 + 7 + 7 = 30


关键要点总结

  • 贪心选择:始终选择当前可用的、不构成环的最小权边。

  • 环检测工具并查集是高效实现 Kruskal 算法的关键,它能近乎 O(1) 时间完成“查找”和“合并”操作。

  • 时间复杂度

    • 排序边:O(|E| log |E|),其中 |E| 是边数。

    • 并查集操作:近似 O(|E| α(|V|)),其中 α 是增长极慢的反阿克曼函数。

    • 总复杂度主要取决于排序:O(|E| log |E|)。

  • 适用场景:适用于稀疏图(边数 |E| 远小于顶点数 |V|² 的图),因为其复杂度主要由边数决定。


【参考代码】

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 边的结构体
struct Edge {
    int u;
    int v;
    int weight;
};
// 并查集结构体
struct UnionFind {
    vector<int> parent;  
    vector<int> rank;
};

// 初始化并查集
void initUnionFind(UnionFind& uf, int n) {
    uf.parent.resize(n);
    uf.rank.resize(n, 0);
    for (int i = 0; i < n; ++i) {
        uf.parent[i] = i;
    }
}

// 查找操作
int find(UnionFind& uf, int x) {
    if (uf.parent[x] != x) {
        uf.parent[x] = find(uf, uf.parent[x]);
    }
    return uf.parent[x];
}

// 合并操作
bool merge(UnionFind& uf, int x, int y) {
    int rootX = find(uf, x);
    int rootY = find(uf, y);
    if (rootX == rootY) return false;
    
    if (uf.rank[rootX] < uf.rank[rootY]) {
        uf.parent[rootX] = rootY;
    } else if (uf.rank[rootX] > uf.rank[rootY]) {
        uf.parent[rootY] = rootX;
    } else {
        uf.parent[rootY] = rootX;
        uf.rank[rootX]++;
    }
    return true;
}

// 比较函数
bool cmp(Edge a, Edge b) {
    return a.weight < b.weight;
}

// Kruskal算法
vector<Edge> kruskalMST(int n, vector<Edge>& edges) {
    // 1. 按边权升序排序
    sort(edges.begin(), edges.end(), cmp);
    // 2. 初始化并查集
    UnionFind uf;
    initUnionFind(uf, n);
    // 3. 构建最小生成树
    vector<Edge> mst;
    int mstWeight = 0;
    for (size_t i = 0; i < edges.size(); ++i) {
        const Edge& edge = edges[i];
        if (merge(uf, edge.u, edge.v)) {
            mst.push_back(edge);
            mstWeight += edge.weight;
            if (mst.size() == (size_t)(n - 1)) break;
        }
    }
    cout << "最小生成树总权重: " << mstWeight << endl;
    return mst;
}

int main() {
    int n = 6;
    vector<Edge> edges;
    // 添加边
    edges.push_back({0, 1, 4});
    edges.push_back({0, 2, 4});
    edges.push_back({1, 2, 2});
    edges.push_back({2, 3, 3});
    edges.push_back({2, 5, 2});
    edges.push_back({2, 4, 4});
    edges.push_back({3, 4, 3});
    edges.push_back({4, 5, 3});
    
    vector<Edge> mst = kruskalMST(n, edges);
    
    cout << "最小生成树包含的边:" << endl;
    for (size_t i = 0; i < mst.size(); ++i) {
        const Edge& edge = mst[i];
        cout << edge.u << " - " << edge.v << " : " << edge.weight << endl;
    }
    
    return 0;
}


输出

最小生成树总权重: 14
最小生成树包含的边:
1 - 2 : 2
2 - 5 : 2
2 - 3 : 3
3 - 4 : 3
0 - 1 : 4


    扫描二维码推送至手机访问。

    版权声明:本文由青少年编程知识记录发布,如需转载请注明出处。

    分享给朋友:

    相关文章

    拓扑排序

    拓扑排序

    一、定义对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则...

    【数据结构】栈(Stack)的介绍

    栈是只能在某一端插入和删除的特殊线性表。栈就是一种类似桶堆积物品的数据结构,进行删除和插入的一端称栈顶,另一端称栈底。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为后进先出表(LIF...

    【题解】围圈报数(约瑟夫问题)

    【题解】围圈报数(约瑟夫问题)

    【题目描述】有n个人依次围成一圈,从第1个人开始报数,数到第m个人出列,然后从出列的下一个人开始报数,数到第m个热呢又出列,... ,如此反复到所有的人全部出列为止。设n个人的编号分别为1,2,......

    CSP-J2021年普及组复赛T4——小熊的果篮

    【题目描述】    小熊的水果店里摆放着一排 n 个水果。每个水果只可能是苹果或桔子,从左到右依 次用正整数 1、2、3、……、n 编号。连续排在一起的同一种...

    【STL】二分查找函数 lower_bound 和 upper_bound

    一、 lower_bound【功能】在数组a中从a[begin]开始到a[end - 1]按照cmp函数来比较进行二分查找第一个大于等于k的数的地址,如果有第一个大于等于k的数则返回该数的地...

    STL入门——简单介绍

    一、STL是什么?    STL(Standard Template Library)即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ S...