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

代码随想录Day57:图论(寻宝prim算法精讲kruskal算法精讲)

一、实战

prim算法精讲

53. 寻宝(第七期模拟笔试)

最小生成树:所有节点的最小连通子图,即:以最小的成本(边的权值)将图中所有节点链接到一起。图中有n个节点,那么一定可以用n-1条边将所有节点连接到一起。

prim算法:从节点的角度采用贪心的策略每次寻找距离最小生成树最近的节点并加入到最小生成树中。(minDist数组是prim算法的灵魂,它帮助prim算法完成最重要的一步,就是如何找到距离最小生成树最近的点。)

初始化的时候,还没有最小生成树,默认每个节点距离最小生成树是最大的,这样后面我们在比较的时候,发现更近的距离,才能更新到minDist数组上

prim三部曲:

  1. 第一步,选距离生成树最近节点。选择距离最小生成树最近的节点,加入到最小生成树,刚开始还没有最小生成树,所以随便选一个节点加入就好(因为每一个节点一定会在最小生成树里,所以随便选一个就好),那我们选择节点1
  2. 第二步,最近节点加入生成树。此时节点1已经算最小生成树的节点。
  3. 第三步,更新非生成树节点到生成树的距离(即更新minDist数组。minDist数组用来记录每一个节点距离最小生成树的最近距离。)此时所有非生成树的节点距离最小生成树(节点1)的距离都已经跟新了。

注意:下标0不用管,下标1与节点1对应,这样可以避免把节点搞混。

后面就是重复三部曲的过程。

节点2,3,5距离最小生成树(节点1)最近,这里就近选节点2,其它也行。此时节点1和节点2,已经算最小生成树的节点。更新节点距离最小生成树的距离
为什么只比较节点4和节点3的距离呢?因为节点3加入最小生成树后,非生成树节点只有节点4和3是链接的,所以需要重新更新一下节点4距离最小生成树的距离,其他节点距离最小生成树的距离不变

最后,minDist数组也就是记录的是最小生成树所有边的权值,要求最小生成树里边的权值总和就是把最后的minDist数组累加一起。

import java.util.*;public class Main{public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int v = scanner.nextInt(); // 读入顶点数int e = scanner.nextInt(); // 读入边数// 创建邻接矩阵,表示图中各点之间的边权int[][] grid = new int[v+1][v+1];for (int i = 0; i <= v; i++) {Arrays.fill(grid[i], Integer.MAX_VALUE); // 初始化为无穷大,表示无边}// 读入每条边,并填充邻接矩阵(无向图,双向赋值)for (int i = 0; i < e; i++) {int x = scanner.nextInt(); // 边的起点int y = scanner.nextInt(); // 边的终点int k = scanner.nextInt(); // 边的权重grid[x][y] = k; // 正向边grid[y][x] = k; // 反向边(无向图)}// minDist[i] 表示当前生成树到节点 i 的最短边权int[] minDist = new int[v+1];Arrays.fill(minDist, Integer.MAX_VALUE); // 初始化所有距离为无穷大boolean[] isInTree = new boolean[v + 1]; // 标记节点是否已加入最小生成树// Prim 算法主循环:需要加入 n-1 个节点(除起点外)for (int i = 1; i < v; i++) {int cur = 1;                    // 临时变量:用于记录当前要加入的节点int minValue = Integer.MAX_VALUE; // 临时变量:记录当前最小距离// 遍历所有节点,寻找未加入树中且距离最小的节点for(int j = 1; j <= v; j++) {if(!isInTree[j] && minDist[j] < minValue) {minValue = minDist[j]; // 更新最小距离cur = j;               // 记录该节点}}isInTree[cur] = true; // 将找到的最近节点加入生成树// 用新加入的节点 cur 更新其他节点到生成树的最短距离for(int j = 1; j <= v; j++) {if(!isInTree[j] && grid[cur][j] < minDist[j]) {minDist[j] = grid[cur][j]; // 松弛操作:发现更短边则更新}}}// 累加最小生成树的总权重(注意从 i=2 开始,跳过0和1)int result = 0;for (int i = 2; i <= v; i++) {result += minDist[i]; // 累加每个节点连到生成树的边权}System.out.println(result); // 输出最小生成树的总权重scanner.close(); }
}

kruskal算法精讲

53. 寻宝(第七期模拟笔试)

kruscal的思路:

  • 边的权值排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合

将图中的边按照权值有小到大排序,这样从贪心的角度来说,优先选 权值小的边加入到 最小生成树中。排序后的边顺序为[(1,2) (4,5) (1,3) (2,6) (3,4) (6,7) (5,7) (1,5) (3,2) (2,4) (5,6)]权值相同,前后顺序任意

选边(1,2),节点1 和 节点2 不在同一个集合,所以生成树可以添加边(1,2),并将 节点1,节点2 放在同一个集合
选边(4,5),节点4 和 节点 5 不在同一个集合,生成树可以添加边(4,5) ,并将节点4,节点5 放到同一个集合
选边(1,3),节点1 和 节点3 不在同一个集合,生成树添加边(1,3),并将节点1,节点3 放到同一个集合
选边(2,6),节点2 和 节点6 不在同一个集合,生成树添加边(2,6),并将节点2,节点6 放到同一个集合
选边(3,4),节点3 和 节点4 不在同一个集合,生成树添加边(3,4),并将节点3,节点4 放到同一个集合
选边(6,7),节点6 和 节点7 不在同一个集合,生成树添加边(6,7),并将 节点6,节点7 放到同一个集合

选边(5,7),节点5 和 节点7 在同一个集合,不做计算。选边(1,5),两个节点在同一个集合,不做计算。后面遍历 边(3,2),(2,4),(5,6) 同理,都因两个节点已经在同一集合,不做计算。此时 我们就已经生成了一个最小生成树如上图。

在代码中,如果将两个节点加入同一个集合,又如何判断两个节点是否在同一个集合呢?就是我们之前学习的并查集

二、两种算法的区别

        Kruskal 与 prim 的关键区别在于,prim维护的是节点的集合,而 Kruskal 维护的是边的集合。 如果 一个图中,节点多,但边相对较少,那么使用Kruskal 更优。

        一个图里怎么可能节点多,边却少呢?节点未必一定要连着边那, 例如 这个图,大家能明显感受到边没有那么多对吧,但节点数量 和 上述我们讲的例子是一样的。

        为什么边少的话,使用 Kruskal 更优呢?因为 Kruskal 是对边进行排序的后 进行操作是否加入到最小生成树。边如果少,那么遍历操作的次数就少。在节点数量固定的情况下,图中的边越少,Kruskal 需要遍历的边也就越少。而 prim 算法是对节点进行操作的,节点数量越少,prim算法效率就越优。

        所以在 稀疏图中,用Kruskal更优。 在稠密图中,用prim算法更优。

边数量较少为稀疏图,接近或等于完全图(所有节点皆相连)为稠密图

        Prim 算法 时间复杂度为 O(n^2),其中 n 为节点数量,它的运行效率和图中边树无关,适用稠密图。Kruskal算法 时间复杂度 为 nlogn,其中n 为边的数量,适用稀疏图。

import java.util.*;// 定义一条边:起点、终点、权重
class Edge {int from, to, weight;Edge(int from, int to, int weight) {this.from = from;this.to = to;this.weight = weight;}
}public class Main {// 并查集数组:记录每个节点的“老大”private static int[] parent = new int[10001];// 并查集初始化:每个节点自己是自己的老大public static void initUnionFind() {for (int i = 0; i < 10001; i++) {parent[i] = i;}}// 查找 x 的根节点(路径压缩优化)public static int findRoot(int x) {if (x == parent[x]) return x;return parent[x] = findRoot(parent[x]); // 路径压缩}// 合并两个集合public static void union(int x, int y) {int rootX = findRoot(x);int rootY = findRoot(y);if (rootX == rootY) return; // 已在同一集合parent[rootY] = rootX; // 把 y 的根挂到 x 的根上}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int V = scanner.nextInt(); // 顶点数int E = scanner.nextInt(); // 边数List<Edge> edges = new ArrayList<>(); // 存所有边int totalCost = 0; // 最小总距离// 读入每条边for (int i = 0; i < E; i++) {int v1 = scanner.nextInt();int v2 = scanner.nextInt();int w = scanner.nextInt();edges.add(new Edge(v1, v2, w));}// 按边的长度从小到大排序(贪心策略)edges.sort(Comparator.comparingInt(e -> e.weight));// 初始化并查集initUnionFind();// 遍历每条边(从最短的开始)for (Edge e : edges) {int root1 = findRoot(e.from);int root2 = findRoot(e.to);// 如果两个点还不连通,就加上这条边if (root1 != root2) {totalCost += e.weight; // 累加这条边的长度union(e.from, e.to);   // 把这两个点连起来}// 如果已经连通了,跳过(避免成环)}System.out.println(totalCost); // 输出最小总长度scanner.close();}
}

http://www.xdnf.cn/news/18175.html

相关文章:

  • HT6881:重塑便携式音频体验的高效能功率放大器
  • Paraformer实时语音识别中的碎碎念
  • 将SSL配置迁移到Nacos的步骤
  • HarmonyOS 中的 setInterval的基本使用
  • 分布式机器学习之流水线并行GPipe:借助数据并行来实现模型并行计算
  • 矿物分类系统开发笔记(二):模型训练[删除空缺行]
  • ZooKeeper 一致性模型解析:线性一致性与顺序一致性的平衡
  • VScode ROS文件相关配置
  • 【habitat学习一】Habitat-Lab 配置键文档详解(CONFIG_KEYS.md)
  • 嵌入式开发学习———Linux环境下网络编程学习(三)
  • RAG 面试题(实时更新补充)
  • 学习笔记分享——基于STM32的平衡车项目
  • Ubuntu 和麒麟系统创建新用户 webapp、配置密码、赋予 sudo 权限并禁用 root 的 SSH 登录的详细
  • 【PyTorch】单对象分割项目
  • 八大排序简介
  • Java 11中的Collections类详解
  • 数据结构:二叉树oj练习
  • Kubernetes Pod 控制器
  • 【PyTorch项目实战】OpenNMT本地机器翻译框架 —— 支持本地部署和自定义训练
  • 学习strandsagents的http_request tool
  • 接口性能测试工具 - JMeter
  • IMX6ULL-KERNEL源代码
  • KMM跨平台叛逃实录:SwiftUI与Compose Multiplatform共享ViewModel的混合开发框架(代码复用率85%)
  • RPC高频问题与底层原理剖析
  • 【树莓派】【嵌入式】远程树莓派,解决ping不通问题
  • 应用缓存不止是Redis!——亿级流量系统架构设计系列
  • Windows/Centos 7下搭建Apache服务器
  • Nacos-7--扩展一下:0-RTT和1-RTT怎么理解?
  • Spring Boot 全局异常处理
  • 重置iPhone会删除所有内容吗? 详细回答