最小生成树(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算法并输出最小生成树。

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

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

相关推荐
Fuxiao___4 分钟前
不使用递归的决策树生成算法
算法
我爱工作&工作love我9 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower1 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯1 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
Sunyanhui11 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农1 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
SoraLuna1 小时前
「Mac玩转仓颉内测版10」PTA刷题篇1 - L1-001 Hello World
算法·macos·cangjie
白-胖-子2 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-成绩排序
c++·算法·蓝桥杯·真题·蓝桥等考