最小生成树(Minimum Spanning Tree, MST)

最小生成树(Minimum Spanning Tree, MST) 是指在一个无向加权图中,使得所有节点连通且总权重最小的一棵树。最小生成树具有以下特性:

  • 树的性质:最小生成树包含图中的所有顶点,但没有环。
  • 连通性:最小生成树确保图中的所有顶点都是连通的。
  • 最小权重:最小生成树的边的总权重是所有可能的生成树中最小的。

求解最小生成树的常用算法
1. Kruskal算法:

  • 核心思想是贪心策略,即每次选择权重最小的边,前提是不形成环。
  • 具体步骤:
    (1)将图中的所有边按权重从小到大排序。
    (2)依次选择排序后的边,若加入该边不形成环,则将该边加入生成树。
    (3)重复上述步骤,直到生成树包含图中所有顶点。

以下是Kruskal算法在C++中的实现。这个实现使用了并查集(Union-Find)数据结构来检测和避免环的形成。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 边的数据结构
struct Edge {
   int u, v, weight;
   bool operator<(const Edge& other) const {
       return weight < other.weight;
   }
};

// 并查集数据结构
class UnionFind {
public:
   UnionFind(int n) : parent(n), rank(n, 0) {
       for (int i = 0; i < n; ++i) {
           parent[i] = i;
       }
   }

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

   bool unite(int u, int v) {
       int rootU = find(u);
       int rootV = find(v);
       if (rootU != rootV) {
           if (rank[rootU] < rank[rootV]) {
               swap(rootU, rootV);
           }
           parent[rootV] = rootU;
           if (rank[rootU] == rank[rootV]) {
               ++rank[rootU];
           }
           return true;
       }
       return false;
   }

private:
   vector<int> parent;
   vector<int> rank;
};

// Kruskal算法实现
vector<Edge> kruskal(int n, vector<Edge>& edges) {
   sort(edges.begin(), edges.end());
   UnionFind uf(n);
   vector<Edge> mst;

   for (const Edge& edge : edges) {
       if (uf.unite(edge.u, edge.v)) {
           mst.push_back(edge);
       }
   }
   return mst;
}

int main() {
   int n = 4; // 图中顶点的数量
   vector<Edge> edges = {
       {0, 1, 1},
       {0, 2, 2},
       {0, 3, 3},
       {1, 3, 1},
       {2, 3, 4}
   };

   vector<Edge> mst = kruskal(n, edges);

   cout << "Minimum Spanning Tree:" << endl;
   for (const Edge& edge : mst) {
       cout << edge.u << " - " << edge.v << " : " << edge.weight << endl;
   }

   return 0;
}

代码解释

  • Edge 结构体: 表示一条边,包括边的两个顶点 u u u 和 v v v,以及边的权重 w e i g h t weight weight。重载了小于运算符,以便按权重排序。

  • UnionFind 类: 并查集实现,用于管理顶点集合,并支持路径压缩和按秩合并。

    • find 方法:查找顶点的根,使用路径压缩优化。
    • unite 方法:合并两个集合,使用按秩合并优化,避免形成环。
  • kruskal 函数: Kruskal算法的实现。

    • 按权重对边进行排序。
    • 使用并查集管理顶点集合,逐条检查边,若添加该边不形成环,则将其添加到生成树中。
  • main 函数: 设置图的顶点和边,调用 Kruskal 算法并输出最小生成树。

2. Prim算法:

  • 核心思想也是贪心策略,但每次选择与生成树最近的顶点加入生成树。
  • 具体步骤:
    (1)从任意一个顶点开始,标记为已访问。
    (2)选择一条连接已访问顶点和未访问顶点的权重最小的边,将对应的未访问顶点加入生成树。
    (3)重复上述步骤,直到所有顶点都被访问。

以下是Prim算法在C++中的实现。这个实现使用了优先队列(堆)来选择权重最小的边,并逐步构建最小生成树。

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <climits>

using namespace std;

// 定义邻接表中边的结构
typedef pair<int, int> Edge; // first: 权重, second: 目标顶点

// Prim算法实现
void prim(int n, vector<vector<Edge>>& graph) {
    vector<int> parent(n, -1);  // 最小生成树中每个顶点的父节点
    vector<int> key(n, INT_MAX); // 每个顶点到最小生成树的最小权重
    vector<bool> inMST(n, false); // 标记顶点是否已经在最小生成树中

    priority_queue<Edge, vector<Edge>, greater<Edge>> pq;
    pq.push({0, 0}); // 从顶点0开始
    key[0] = 0;

    while (!pq.empty()) {
        int u = pq.top().second;
        pq.pop();

        if (inMST[u]) continue;
        inMST[u] = true;

        for (const auto& [weight, v] : graph[u]) {
            if (!inMST[v] && weight < key[v]) {
                key[v] = weight;
                pq.push({key[v], v});
                parent[v] = u;
            }
        }
    }

    cout << "Minimum Spanning Tree:" << endl;
    for (int i = 1; i < n; ++i) {
        cout << parent[i] << " - " << i << endl;
    }
}

int main() {
    int n = 5; // 图中顶点的数量
    vector<vector<Edge>> graph(n);

    // 添加边(无向图)
    graph[0].emplace_back(2, 1);
    graph[1].emplace_back(2, 0);
    graph[0].emplace_back(3, 3);
    graph[3].emplace_back(3, 0);
    graph[1].emplace_back(3, 3);
    graph[3].emplace_back(3, 1);
    graph[1].emplace_back(8, 4);
    graph[4].emplace_back(8, 1);
    graph[1].emplace_back(6, 2);
    graph[2].emplace_back(6, 1);
    graph[2].emplace_back(7, 4);
    graph[4].emplace_back(7, 2);
    graph[3].emplace_back(5, 4);
    graph[4].emplace_back(5, 3);

    prim(n, graph);

    return 0;
}

代码解释

  • Edge 类型定义typedef pair<int, int> Edge,其中 first 表示权重,second 表示目标顶点。

  • prim 函数: 实现Prim算法,使用优先队列(最小堆)来选择最小权重的边。

    • parent:存储每个顶点在最小生成树中的父节点。
    • key:存储每个顶点到最小生成树的最小权重。
    • inMST:标记每个顶点是否已经包含在最小生成树中。
    • priority_queue:优先队列,按照边的权重从小到大排序,使用 greater<Edge> 使其成为最小堆。
    • 从顶点0开始,将顶点0加入优先队列,并设置其权重为0。
    • 逐步从优先队列中取出权重最小的边,并将对应的顶点加入最小生成树。
    • 更新连接该顶点的所有未包含在最小生成树中的顶点的最小权重。
  • main 函数:设置图的顶点和边,调用Prim算法并输出最小生成树。

    • 构建图的邻接表表示,添加无向边。

这个实现适用于任意无向加权图,并高效地找到最小生成树。

相关推荐
程序喵阿呆2 分钟前
leetcode 162 寻找峰值
数据结构·c++·算法·leetcode
那个那个鱼6 分钟前
C#面:现有一个整数number,请写一个方法判断这个整数是否是2的N次方
开发语言·算法·c#·.net
初眸࿐20 分钟前
美团实习—后端开发凉经
java·功能测试·算法·leetcode·贪心算法·单元测试
hac132240 分钟前
力扣hot100-普通数组
算法·leetcode·代理模式
情系明明41 分钟前
动态规划 剪绳子问题
算法·动态规划
音符犹如代码1 小时前
2734.力扣每日一题6/27 Java(贪心算法)
算法·leetcode·职场和发展
m0_691895841 小时前
第二十七天 第八章 贪心算法 part01 理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
数据结构·算法·leetcode·贪心算法
pin️‍灼灼灼灼1 小时前
Linux——线程练习
linux·服务器·算法
摸鱼的快乐你不懂1 小时前
Leetcode[反转链表]
算法·leetcode·链表
唐果然1 小时前
SAR目标检测
算法·目标检测·目标跟踪