当前位置: 首页 > news >正文

C++ 解决一个简单的图论问题 —— 最小生成树(以 Prim 算法为例)

使用 C++ 解决一个简单的图论问题 —— 最小生成树(以 Prim 算法为例),并且使用 Graphviz 库来生成结果图。

在图论中,“边权之和最小” 是最小生成树(MST)的核心目标,其含义和背景可以从以下几个方面解释:

一、基础定义:什么是 “边权之和”?

  • 边权:图中每条边的权重(Weight),可以代表实际问题中的成本、距离、时间、容量等量化指标。
  • 边权之和:对于一个子图(如生成树),将其中所有边的权重相加得到的总和。

二、“最小” 的具体含义:为什么要让边权之和最小?

在 无向连通图 中,生成树的定义是:

  • 包含图中 所有顶点(必须覆盖所有节点)。
  • 是一棵 (即无环,且边数为 \(|V| - 1\),V 为顶点数)。

最小生成树(MST) 是所有可能的生成树中,边权之和最小的那一棵。 例如,假设有一个包含 4 个顶点的图,可能有多种生成树(如下图示),其中边权和最小的即为 MST:

   顶点A ─(2)─ 顶点B       顶点A ─(1)─ 顶点C│(3)             │(1)顶点C ─(1)─ 顶点D   顶点B ─(3)─ 顶点D边权和:2+3+1=6          边权和:1+1+3=5(MST)

三、数学表达与约束条件

设图 \(G = (V, E)\),其中顶点集合 \(V = \{v_1, v_2, \dots, v_n\}\),边集合 \(E = \{e_1, e_2, \dots, e_m\}\),每条边 \(e_i\) 的权重为 \(w(e_i)\)。 生成树 \(T = (V, E_T)\) 需满足:

  1. \(E_T \subseteq E\),且 \(|E_T| = |V| - 1\)(无环,连通所有顶点)。
  2. 目标是最小化 \(\sum_{e \in E_T} w(e)\),即:\(\min \sum_{e \in E_T} w(e)\)

四、实际意义:为什么需要 “边权之和最小”?

“边权之和最小” 的应用场景通常与 优化问题 相关,例如:

  1. 网络建设
    • 若边权代表铺设电缆的成本,最小生成树表示用最小成本连接所有节点的方案。
  2. 电路设计
    • 边权代表导线长度,MST 可最小化电路中导线的总长度。
  3. 物流规划
    • 边权代表运输距离,MST 可找到连接所有地点的最短路径网络(无环,避免冗余)。

五、与 “生成树” 的本质区别

  • 生成树:只要满足 “包含所有顶点且无环” 即可,不考虑边权和。
  • 最小生成树:在所有生成树中,额外要求边权和 全局最小,是一种带优化目标的生成树。 (注:若图不连通,则不存在生成树;若边权可能为负,Prim 算法仍适用,但 Kruskal 算法需调整)

六、如何求解 “边权之和最小”?

通过 贪心算法(如 Prim 算法、Kruskal 算法)或 优先队列优化 来高效找到 MST,核心思想是:

  • Prim 算法:从一个顶点出发,每次选择当前生成树到未访问顶点的最小边(“加点法”)。
  • Kruskal 算法:按边权从小到大排序,每次选择不构成环的最小边(“加边法”)。

总结

“边权之和最小” 是最小生成树的核心目标,它要求在连通所有顶点的无环子图中,选择边权总和最小的方案。这一概念在实际问题中对应 “最小成本连接所有节点”,通过经典算法可高效求解,是图论中优化问题的基础应用之一。

实现思路

  1. Prim 算法:用于在加权连通图中找到最小生成树。
  2. Graphviz:一个开源的图形可视化工具,通过生成 DOT 语言描述的图形文件,然后转换为图片格式(如 PNG)。

代码解释

  1. Prim 算法:通过优先队列来选择当前最小权值的边,逐步构建最小生成树。
  2. 生成 DOT 文件:将图的信息和最小生成树的边信息写入 DOT 文件,方便后续使用 Graphviz 工具将其转换为图片。

使用方法

  1. 编译并运行上述代码,会生成一个名为 mst.dot 的文件。
  2. 安装 Graphviz 工具,然后在命令行中运行以下命令将 DOT 文件转换为 PNG 图片:
dot -Tpng mst.dot -o mst.png

这样就可以得到一个可视化的最小生成树图片。

Prim 算法与最小生成树问题

1. 什么是 Prim 算法?

Prim 算法是一种用于求解 ** 加权无向图中最小生成树(Minimum Spanning Tree, MST)** 的贪心算法。 最小生成树的定义:包含图中所有顶点,且边权之和最小的无环子图(树结构)。 Prim 算法的核心思想是:从任意一个顶点出发,逐步扩展生成树,每次选择当前生成树到其他顶点的最小边,直到所有顶点都被包含

2. Prim 算法的核心步骤(以邻接表 + 优先队列实现为例)

假设图为 \(G = (V, E)\),顶点集合 \(V = \{0, 1, 2, \dots, n-1\}\),边权非负。

步骤 1:初始化
  • 选择一个起始顶点(如顶点 0),标记为 “已访问”,加入生成树。
  • 维护三个数组:
    • key[]:记录每个顶点到当前生成树的最小边权,初始化为无穷大(起始顶点的key设为 0)。
    • parent[]:记录生成树中每个顶点的父节点,用于重构生成树。
    • visited[]:标记顶点是否已加入生成树。
步骤 2:扩展生成树(贪心选择)
  • 使用 ** 优先队列(最小堆)** 存储顶点,按key值排序,每次取出key最小的顶点 u。
  • 遍历 u 的所有邻接顶点 v:
    • 如果 v 未被访问,且 u 到 v 的边权小于 v 的当前key值:
      • 更新 v 的key值为该边权。
      • 记录 v 的父节点为 u。
      • 将 v 加入优先队列。
步骤 3:重复直到所有顶点加入
  • 重复步骤 2,直到所有顶点被标记为 “已访问”。此时,parent[]数组存储了最小生成树的边关系。
  1. 初始化:起始顶点 0,key[0]=0,其他顶点key为无穷大。
  2. 第一次迭代:取出顶点 0,遍历邻接顶点 1(边权 2)、3(边权 6)。更新它们的key为 2 和 6,父节点为 0。
  3. 第二次迭代:优先队列中最小key是顶点 1(key=2),取出后遍历其邻接顶点 0(已访问)、2(边权 3)、3(边权 8)、4(边权 5)。更新顶点 2 的key为 3,顶点 4 的key为 5,父节点分别为 1。
  4. 第三次迭代:最小key是顶点 2(key=3),取出后遍历邻接顶点 1(已访问)、4(边权 7)。顶点 4 的当前key是 5,7 大于 5,不更新。
  5. 第四次迭代:最小key是顶点 4(key=5),取出后遍历邻接顶点 1(已访问)、2(已访问)、3(边权 9)。顶点 3 的当前key是 6,9 大于 6,不更新。
  6. 第五次迭代:最后取出顶点 3(key=6),所有顶点已访问,算法结束。

最终生成树的边为: 0-1(2)、1-2(3)、1-4(5)、0-3(6),总权值 2+3+5+6=16。

4. 算法复杂度
  • 邻接矩阵 + 数组实现(适合稠密图):时间复杂度 \(O(V^2)\),无需优先队列,每次遍历所有未访问顶点找最小key
  • 邻接表 + 优先队列实现(适合稀疏图):时间复杂度 \(O(E \log V)\),每次更新优先队列的时间为 \(O(\log V)\)。
5. 与 Kruskal 算法的区别
  • Prim 算法加点法,从顶点出发,每次扩展生成树的顶点,适合边稠密或顶点少的图。
  • Kruskal 算法加边法,按边权从小到大排序,每次选不构成环的最小边,适合边稀疏的图。
6. 应用场景
  • 网络设计(如最小成本连接所有节点)。
  • 电路设计(最小化导线长度)。
  • 聚类问题(构建最小生成树后按边权切割)。
总结

Prim 算法通过贪心策略,每次选择当前生成树到未访问顶点的最小边,逐步构建最小生成树。其核心是局部最优选择(最小边权)推导出全局最优解,适用于边权非负的无向图,是解决最小生成树问题的经典算法之一。

#include <iostream>
#include <vector>
#include <queue>
#include <fstream>
#include <limits>using namespace std;// 定义边的结构体
struct Edge {int to;int weight;Edge(int t, int w) : to(t), weight(w) {}
};// 定义图的邻接表表示
using Graph = vector<vector<Edge>>;// Prim 算法求最小生成树
vector<Edge> prim(const Graph& graph) {int n = graph.size();vector<bool> visited(n, false);vector<Edge> mst;priority_queue<pair<int, pair<int, int>>, vector<pair<int, pair<int, int>>>, greater<pair<int, pair<int, int>>>> pq;// 从顶点 0 开始visited[0] = true;for (const Edge& edge : graph[0]) {pq.push({ edge.weight, {0, edge.to} });}while (!pq.empty()) {auto [weight, nodes] = pq.top();pq.pop();int u = nodes.first;int v = nodes.second;if (visited[v]) continue;visited[v] = true;mst.emplace_back(v, weight);for (const Edge& edge : graph[v]) {if (!visited[edge.to]) {pq.push({ edge.weight, {v, edge.to} });}}}return mst;
}// 生成 DOT 文件
void generateDotFile(const Graph& graph, const vector<Edge>& mst, const string& filename) {ofstream dotFile(filename);if (!dotFile.is_open()) {cerr << "无法打开文件: " << filename << endl;return;}dotFile << "graph G {" << endl;// 绘制所有边for (int u = 0; u < graph.size(); ++u) {for (const Edge& edge : graph[u]) {if (u < edge.to) {dotFile << "  " << u << " -- " << edge.to << " [label=\"" << edge.weight << "\"];" << endl;}}}// 突出显示最小生成树的边for (const Edge& edge : mst) {int u = -1; // 这里需要根据 mst 找到对应的 u,假设第一个节点是 0for (int i = 0; i < graph.size(); ++i) {for (const Edge& e : graph[i]) {if (e.to == edge.to && e.weight == edge.weight) {u = i;break;}}if (u != -1) break;}dotFile << "  " << u << " -- " << edge.to << " [color=red, penwidth=3];" << endl;}dotFile << "}" << endl;dotFile.close();
}int main() {// 示例图Graph graph = {{{1, 2}, {3, 6}},{{0, 2}, {2, 3}, {3, 8}, {4, 5}},{{1, 3}, {4, 7}},{{0, 6}, {1, 8}, {4, 9}},{{1, 5}, {2, 7}, {3, 9}}};// 计算最小生成树vector<Edge> mst = prim(graph);// 生成 DOT 文件generateDotFile(graph, mst, "mst.dot");cout << "最小生成树的边已计算,DOT 文件已生成。" << endl;return 0;
}

相关文章:

  • <uniapp><插件><UTS>在uniapp中,创建自己的插件并发布到uni插件市场
  • Ubuntu安装SRS流媒体服务
  • 人智交互中的AI世代
  • 2025医疗领域AI发展五大核心趋势与路线研究
  • List--链表
  • C++ 表达式求值优先级、结合律与求值顺序(五十九)
  • 维度的语法:从列表的散文到 ndarray 的十四行诗
  • PostgreSQL oracle_fdw 扩展解析
  • 【DeepSeek认证】最好的MODBUS调试工具
  • 【JAVA ee初阶】多线程(3)
  • 设计模式(状态模式)
  • 2025年- H11-Lc118-53.最大子数组和(普通数组)---java版
  • WPF 程序监控硬件设备状态变化的实现方案
  • MaxScript二维图形布尔(并)运算
  • 【iOS】OC源码阅读——alloc源码分析
  • Android显示学习笔记本
  • 第一天 车联网定义、发展历程与生态体系
  • 机器学习中的标签策略:直接标签、代理标签与人工数据生成
  • 清华大学正式成立人工智能医院
  • 北重数控滑台加工厂家:汽车零部件试验铁地板-安全性能的测试方法
  • 解放日报头版聚焦“人民城市”:共建共享展新卷
  • 北汽蓝谷一季度净亏损9.5亿元,拟定增募资不超60亿元
  • 广东雷州农商行董事长、原行长同日被查
  • 稳就业稳经济五方面若干举措将成熟一项出台一项
  • 新加坡选情渐热:播客、短视频各显神通,总理反对身份政治
  • “70后”通化市委书记孙简已任吉林省政府领导