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

代码随想录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.lryc.cn/news/626938.html

相关文章:

  • 3D检测笔记:相机模型与坐标变换
  • 今日行情明日机会——20250820
  • 算法提升树形数据结构-(线段树)
  • 数据结构与算法系列(大白话模式)小学生起点(一)
  • 关于 Flask 3.0+的 框架的一些复习差异点
  • 算法230. 二叉搜索树中第 K 小的元素
  • 雷卯针对香橙派Orange Pi 5B开发板防雷防静电方案
  • 力扣hot100:最大子数组和的两种高效方法:前缀和与Kadane算法(53)
  • Deepseek+python自动生成禅道测试用例
  • 自动化测试用例生成:基于Python的参数化测试框架设计与实现
  • 记一次pnpm start启动异常
  • Spring Boot 3整合Nacos,配置namespace
  • 质谱数据分析环节体系整理
  • Rust 入门 包 (二十一)
  • 内网环境给VSCode安装插件
  • PostgreSQL 流程---更新
  • 基于51单片机自动浇花1602液晶显示设计
  • Notepad++批量转UTF-8脚本
  • 测试DuckDB插件对不同格式xlsx文件的读写效率
  • 基于Pytochvideo训练自己的的视频分类模型
  • 【C++】基础:C++11-14-17常用新特性介绍
  • XR(AR/VR/MR)芯片方案,Soc VS “MCU+协处理器”?
  • 109、【OS】【Nuttx】【周边】效果呈现方案解析:workspaceStorage(下)
  • 【最后203篇系列】034 使用SQLite构建简单的任务管理
  • 解决Docker 无法连接到官方镜像仓库
  • LINUX 820 shell:shift,expect
  • 49 C++ STL模板库18-类模板-pair
  • 双模式 RTMP H.265 播放器解析:从国内扩展到 Enhanced RTMP 标准的演进
  • 深入理解JVM内存结构:从字节码执行到垃圾回收的全景解析
  • 基于单片机智能加湿器/空气加湿器