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

【最小生成树】(三) Prim 算法

引入

在最小生成树的前两个章节中, 我们介绍了并查集以及基于并查集的 Kruskal 算法;

不难看出, Kruskal 算法的时间复杂度主要来自于对所有边按权值排序; 假设图中共有 E 条边, 那么 Kruskal 的时间复杂度为 ElogE;

因为我们在并查集中使用了路径压缩算法, 其时间复杂度接近 O(1), 所以整个算法的时间复杂度和排序算法相当, 在使用快速排序的情况下, 就是 ElogE;

如果是稠密图(相对于顶点数量来说, 边的数量多出很多), Kruskal 算法的时间复杂度就会比较高, 这时, 使用 Prim 算法能得到更小的时间复杂度;

Prim 算法也很简单, 选择任意一个顶点开始构建树, 每次选择距离树最近的结点加入树中, 并用新加入的结点, 尝试更新其余未加入结点到树的最小距离; 如此循环, 直到处理完所有顶点;

题目链接: <<寻找宝藏>>

在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。

不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来(注意:这是一个无向图)。

给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。

解读: 明显, 就是要我们以最小的代价, 将所有点连通, 典型的最小生成树问题;

代码

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], 10001);}// 读取边的信息并填充邻接矩阵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;}// 所有节点到最小生成树的最小距离int[] minDist = new int[v + 1];Arrays.fill(minDist, 10001);// 记录节点是否在树里boolean[] isInTree = new boolean[v + 1];// Prim算法主循环for (int i = 1; i < v; i++) {// 选择距离生成树最近的节点int cur = -1;int minVal = Integer.MAX_VALUE;for (int j = 1; j <= v; j++) {if (!isInTree[j] && minDist[j] < minVal) {minVal = minDist[j];cur = j;}}// 将最近的节点加入生成树isInTree[cur] = true;// 更新非生成树节点到生成树的距离for (int j = 1; j <= v; j++) {if (!isInTree[j] && grid[cur][j] < minDist[j]) {minDist[j] = grid[cur][j];}}}// 统计结果int result = 0;for (int i = 2; i <= v; i++) {result += minDist[i];}System.out.println(result);scanner.close();}
}

堆优化代码

如果不采用堆, 时间复杂度为 O(V²), 适用于稠密图;

如果采用堆优化, 时间复杂度就和 Kruskal 算法一致了, 适用于稀疏图, 但代码比 Kruskal 复杂得多, 不如直接用 Kruskal;

private static void prim(){Scanner sc = new Scanner(System.in);// 顶点数量int vNum = sc.nextInt();// 边的数量int eNum = sc.nextInt();// 临接表保存所有边List<List<int[]>> graph = new ArrayList<>(vNum + 1);for(int i = 0; i <= vNum; i++){graph.add(new ArrayList<>());}for(int i = 0; i < eNum; i++){int nodeA = sc.nextInt();int nodeB = sc.nextInt();int val = sc.nextInt();// 因为是无向图, 所以两个方向都要添加;// 因为 Prim 算法适用于稠密图, 所以用临接矩阵保存也完全可以, 大家自己可以写邻接矩阵的版本graph.get(nodeA).add(new int[]{nodeB, val});graph.get(nodeB).add(new int[]{nodeA, val});}// 记录顶点目前是否在构建的树中; 因为题目规定顶点从1开始, 所以这里多申请一个空间boolean[] inTree = new boolean[vNum + 1];// 顶点到目前已构建的树的最小距离;int[] dist = new int[vNum + 1];Arrays.fill(dist, Integer.MAX_VALUE);// 采用堆优化, 每次取出当前距离树最近的结点;// i[0] = node, i[1] = val;PriorityQueue<int[]> heap = new PriorityQueue<>((i1, i2)->Integer.compare(i1[1], i2[1]));heap.add(new int[]{1, 0}); // 先把结点1加入最小生成树; 选择任意结点都可以;int res = 0; // 记录结果// 只需要选择 n - 1条边;for(int i = 0; i < vNum -1; i++){int[] curNode = heap.poll();// 当我们更新一个顶点到树的最小距离时, 直接将更新后的结果加入了堆中, 没有删除原来的;// 所以可能出现顶点已经处理过了, 后面又再次遇到的情况; 需要跳过;if(inTree[curNode[0]]){continue;}inTree[curNode[0]] = true;res += curNode[1];// 对于还未加入树的结点, 考虑由curNode 连接到树的代价, 是否更小了, 如果是, 更新最小距离;List<int[]> edges = graph.get(curNode[0]);for(int[] edge : edges){if(!inTree[edge[0]] && edge[1] < dist[edge[0]]){dist[edge[0]] = edge[1];heap.add(new int[]{edge[0], edge[1]});}}}}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 某通用系统0day审计过程
  • Leetcode - 周赛409
  • glTF的基本结构
  • 【OpenHarmony】openharmony移植到RK3568------搭建开发环境
  • Spring——Second
  • AI赋能周界安防:智能视频分析技术构建无懈可击的安全防线
  • c++版opencv长文指南
  • Java进阶篇之深入理解多态的概念与应用
  • PHP项目任务系统小程序源码
  • 【网络基础一】几乎不讲任何网络协议细节,搭建网络基本结构
  • 【vue】在页面右下角添加悬浮按钮组件
  • 循环神经网络六-Pytorch中的序列化器
  • DreamFusion 论文学习
  • HTTP 和 HTTPS 协议的全面介绍
  • springboot多数据源配置
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • Angular 2 DI - IoC DI - 1
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • crontab执行失败的多种原因
  • exif信息对照
  • JSDuck 与 AngularJS 融合技巧
  • mysql常用命令汇总
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • scrapy学习之路4(itemloder的使用)
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • webgl (原生)基础入门指南【一】
  • 多线程事务回滚
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 来,膜拜下android roadmap,强大的执行力
  • 力扣(LeetCode)357
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 前端
  • 网页视频流m3u8/ts视频下载
  • 微信开放平台全网发布【失败】的几点排查方法
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 【干货分享】dos命令大全
  • Prometheus VS InfluxDB
  • 浅谈sql中的in与not in,exists与not exists的区别
  • ​水经微图Web1.5.0版即将上线
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (C11) 泛型表达式
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (Ruby)Ubuntu12.04安装Rails环境
  • (八)Flink Join 连接
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (十一)c52学习之旅-动态数码管
  • (一)u-boot-nand.bin的下载
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .Net Core 微服务之Consul(二)-集群搭建
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .Net FrameWork总结
  • .net 按比例显示图片的缩略图