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

图论(5)最小生成树算法

目录

一、概念介绍

二、两种典型的MST算法

1. Prim 算法

2. Kruskal 算法

三、两种算法对比


一、概念介绍

无向连通加权图中,生成树是一个包含所有顶点无环子图。 如果我们为每条边赋予权重(通常表示成本、距离、时间等),则最小生成树所有生成树中权重之和最小的那棵树

最小生成树的性质:

  1. 顶点数固定:MST包含图中所有的顶点。

  2. 边数为 n-1:对于 n 个顶点的连通无向图,生成树恰好有 n-1 条边。

  3. 无环性:MST中不允许出现环。

  4. 最小权重:总权重在所有生成树中是最小的。

应用场景:

  • 网络设计:构建通信网络、电网、计算机网络时,最小化建设成本。

  • 道路规划:最小化公路、铁路或管道的建设长度。

  • 聚类分析:在数据聚类中用MST来发现数据的结构。

  • 图像处理:用于图像分割等计算机视觉任务。

  • 物流配送:寻找最经济的配送路径(不是最短路径问题,而是覆盖所有地点的最小代价)。

概念理解:

  1. 节点(Node) 图中的顶点,代表实体(例如城市、网络设备、仓库等)。

  2. 边(Edge) 图中连接两个顶点的线段,带有权重(例如距离、费用、延迟等)。

  3. 权重(Weight) 边的数值属性,用于衡量连接两个节点的“成本”。

  4. 无向图 边的方向不区分“起点”和“终点”,即边 (u, v)(v, u) 等价。

  5. 连通性 从任意节点可以到达其他任意节点的图称为连通图。

  6. 无环性 图中不存在回路。生成树一定是无环的。

  7. 并查集(Union-Find) 一种用于检测两个顶点是否属于同一个连通分量的数据结构,常用于 Kruskal 算法中避免形成环。

二、两种典型的MST算法

1. Prim 算法

  • 算法思想

    Prim 算法从某个起始节点出发,不断选择权重最小且能连接新的未访问节点的边,直到所有节点都被包含进来。 其过程类似于“从一个点出发,不断扩展到最近的未访问节点”。

  • 核心步骤

    1. 从任意一个顶点开始,将其加入已访问集合

    2. 将该顶点的所有边加入优先队列(按权重排序)。

    3. 从优先队列中取出权重最小的边,如果它连接的节点未被访问,则将该节点加入已访问集合,并将其边加入优先队列。

    4. 重复步骤3,直到所有顶点都被访问或队列为空。

Java实现(Prim)

package graph;
​
import java.util.*;
​
/*** 无向图 无环 连通*/
public class MinimumSpanningTrees {
​
​/*** prim算法求解最小生成树** @param nodes*/public static List<Edge> primMST(List<Node> nodes) {if (nodes.isEmpty()) return Collections.emptyList();
​List<Edge> mst = new ArrayList<>();PriorityQueue<Edge> edgeQueue = new PriorityQueue<>(Comparator.comparingInt(e -> e.weight)); // 存储访问过的节点集合对应的边集合,并且按照权重排序Set<Node> visited = new HashSet<>(); // 存储访问过的节点集合
​// 从第一个节点开始Node startNode = nodes.get(3);visited.add(startNode);edgeQueue.addAll(startNode.adj);
​while (!edgeQueue.isEmpty() && visited.size() < nodes.size()) {Edge minEdge = edgeQueue.poll();Node nextNode = minEdge.to;
​if (!visited.contains(nextNode)) {visited.add(nextNode);mst.add(minEdge);
​// 添加新节点的所有边到优先队列for (Edge edge : nextNode.adj) {if (!visited.contains(edge.to)) {edgeQueue.add(edge);}}}}
​return mst;}
​
​public static void main(String[] args) {
​Node v1 = new Node("v1");Node v2 = new Node("v2");Node v3 = new Node("v3");Node v4 = new Node("v4");Node v5 = new Node("v5");Node v6 = new Node("v6");Node v7 = new Node("v7");
​v1.addEdge(v2,2);v1.addEdge(v3,4);v1.addEdge(v4,1);
​v2.addEdge(v1,2);v2.addEdge(v5,10);v2.addEdge(v4,3);
​v3.addEdge(v1,4);v3.addEdge(v4,2);v3.addEdge(v6,5);
​v4.addEdge(v1,1);v4.addEdge(v2,3);v4.addEdge(v3,2);v4.addEdge(v5,7);v4.addEdge(v6,8);v4.addEdge(v7,4);
​v5.addEdge(v2,10);v5.addEdge(v4,7);v5.addEdge(v7,6);
​v6.addEdge(v3,5);v6.addEdge(v4,8);v6.addEdge(v7,1);
​v7.addEdge(v4,4);v7.addEdge(v5,6);v7.addEdge(v6,1);
​List<Node> nodes = Arrays.asList(v1, v2, v3, v4, v5, v6, v7);
​List<Edge> edges = primMST(nodes);System.out.println("Total weight: " + edges.stream().map(item -> item.weight).reduce(0, Integer::sum));for(Edge edge:edges){System.out.println(edge.from.name + "->" + edge.to.name + ":" + edge.weight);}
​}
​
}
  • 执行结果

    Total weight: 16
    v4->v1:1
    v4->v3:2
    v1->v2:2
    v4->v7:4
    v7->v6:1
    v7->v5:6

2. Kruskal 算法

  • 算法思想

    Kruskal 算法是一种“边驱动”策略,按权重升序对所有边排序,然后依次选择最小的边加入生成树,但前提是不会形成环。 使用并查集判断加入边后是否形成环。

  • 核心步骤

    1. 将所有边按权重升序排序。

    2. 初始化并查集,每个节点是一个独立集合。

    3. 依次取最小的边,若该边的两个端点属于不同集合,则将其加入MST,并合并两个集合。

    4. 当MST中边数达到 n-1 时结束。

Java实现(Kruskal)

package graph;
​
import java.util.*;
​
/*** 无向图 无环 连通*/
public class MinimumSpanningTrees {
​/*** Kruskal算法求解最小生成树** @param nodes 图中的所有节点* @return 最小生成树的边集合*/public static List<Edge> kruskalMST(List<Node> nodes) {if (nodes.isEmpty()) return Collections.emptyList();
​List<Edge> mst = new ArrayList<>();
​// 收集所有边,按权重从小到大排序List<Edge> allEdges = new ArrayList<>();for (Node node : nodes) {allEdges.addAll(node.adj);}allEdges.sort(Comparator.comparingInt(e -> e.weight));
​
​// 使用【并查集】来检测环UnionFind uf = new UnionFind(nodes);
​for (Edge edge : allEdges) {Node from = edge.from;Node to = edge.to;
​if (!uf.isConnected(from, to)) { // 判断两个集合能否联通mst.add(edge);uf.union(from, to);}
​// 如果已经连接了所有节点,提前退出if (mst.size() == nodes.size() - 1) {break;}}
​return mst;}
​
​public static void main(String[] args) {
​Node v1 = new Node("v1");Node v2 = new Node("v2");Node v3 = new Node("v3");Node v4 = new Node("v4");Node v5 = new Node("v5");Node v6 = new Node("v6");Node v7 = new Node("v7");
​v1.addEdge(v2,2);v1.addEdge(v3,4);v1.addEdge(v4,1);
​v2.addEdge(v1,2);v2.addEdge(v5,10);v2.addEdge(v4,3);
​v3.addEdge(v1,4);v3.addEdge(v4,2);v3.addEdge(v6,5);
​v4.addEdge(v1,1);v4.addEdge(v2,3);v4.addEdge(v3,2);v4.addEdge(v5,7);v4.addEdge(v6,8);v4.addEdge(v7,4);
​v5.addEdge(v2,10);v5.addEdge(v4,7);v5.addEdge(v7,6);
​v6.addEdge(v3,5);v6.addEdge(v4,8);v6.addEdge(v7,1);
​v7.addEdge(v4,4);v7.addEdge(v5,6);v7.addEdge(v6,1);
​List<Node> nodes = Arrays.asList(v1, v2, v3, v4, v5, v6, v7);
​List<Edge> edges = kruskalMST(nodes);System.out.println("Total weight: " + edges.stream().map(item -> item.weight).reduce(0, Integer::sum));for(Edge edge:edges){System.out.println(edge.from.name + "->" + edge.to.name + ":" + edge.weight);}
​}
}
  • 并查集(Union-Find)实现

/*** 并查集*/
class UnionFind {
​private Map<Node, Node> parent; // 构造森林private Map<Node, Integer> rank;
​public UnionFind(List<Node> nodes) {parent = new HashMap<>();rank = new HashMap<>();
​for (Node node : nodes) {parent.put(node, node);rank.put(node, 0);}}
​public Node find(Node node) {if (parent.get(node) != node) {parent.put(node, find(parent.get(node))); // 递归路径压缩}return parent.get(node);}
​public boolean isConnected(Node x, Node y) {return find(x) == find(y);}
​public void union(Node x, Node y) {Node rootX = find(x);Node rootY = find(y);
​if (rootX == rootY) return;
​// 按秩合并if (rank.get(rootX) > rank.get(rootY)) {parent.put(rootY, rootX);} else if (rank.get(rootX) < rank.get(rootY)) {parent.put(rootX, rootY);} else {parent.put(rootY, rootX);rank.put(rootX, rank.get(rootX) + 1);}}
}

三、两种算法对比

算法思路数据结构复杂度适用场景
Prim从点出发扩展最小边优先队列 + 集合O(E log V)稠密图
Kruskal从小边开始构建树排序 + 并查集O(E log E)稀疏图
  • Prim 更适合稠密图(边很多),因为它每次只扩展到一个新的节点。

  • Kruskal 更适合稀疏图(边少),因为它先全局排序再选择边

http://www.dtcms.com/a/331183.html

相关文章:

  • Claude Code 国内直接使用,原生支持 Windows 免WSL安装教程
  • Day56--图论--108. 冗余的边(卡码网),109. 冗余的边II(卡码网)
  • Day58--图论--117. 软件构建(卡码网),47. 参加科学大会(卡码网)
  • MySQL窗口函数与PyMySQL以及SQL注入
  • MySQLl中OFFSET 的使用方法
  • 中国AI生态加速迭代,AI硬件引领人机互动新范式
  • LeetCode 分类刷题:2302. 统计得分小于 K 的子数组数目
  • Gradle(四)Maven 项目迁移 Gradle 项目实践
  • 文件服务器:samba
  • Java 并发新范式:用 Structured Concurrency 优雅收拾多线程烂摊子
  • 编排之神-Kubernetes微服务专题--ingress-nginx及金丝雀Canary的演练
  • 电动自行车:中国式制霸
  • 支付域——账户系统设计
  • 2025年Java大厂面试场景题全解析:高频考点与实战攻略
  • 优德普SAP一体化平台有哪些功能?
  • 力扣(盛最多水的容器)
  • Java基础 8.14
  • 力扣-5.最长回文子串
  • MySQL的索引(索引的创建和设计原则):
  • 初识c语言————缓冲区字符滞留
  • 天马 TM150XDHG01-04 宽温高亮液晶模组技术档案
  • **标题:发散创新,探索编程中的平衡设计****摘要**:本文将探讨如何在编程中运用平衡设计思想,通过实例分析与
  • STM32F103 basic定时器的介绍和应用
  • 2021-2025全国监测国控断面地表水水质数据
  • P12348 [蓝桥杯 2025 省 A 第二场] 交互
  • 每日任务day0814:小小勇者成长记之钓鱼日记(字典推导式)
  • gpt2架构学习(1)
  • PDM 如何通过 ERP/PLM 释放数据价值?
  • 力扣面试150(56/150)
  • CodeTop 复习