修路问题(普里姆算法)

  • 最小生成树,给定一个带权的无向连通图,如何选择一颗生成树,使树上所有边上权的总和为最小;N个顶点,N-1条边
  • 普里姆算法,在包含n个顶点的连通图中,找出只有n-1条边,包含所有n个顶点的连通子图,极小连通子图
  • 创建最小生成树,每个顶点的之间都选去最小的权值作为连通点;由于每个顶点都有两种状态访问过/没有被访问过,那么两层for循环,就可以保证所有顶点都被扫描到,再通过mGraph.weight[i][j] < minWeight,保证再一次子图的遍历中,获取的边(向外生长的边)一定是最小的
class MinTree {//图的邻接矩阵/*** @param graph 图对象* @param verxs 定点个数* @param data 图的各个定点的值* @param weight 图的邻接矩阵*/public void createGraph(MGraph graph, int verxs, char[] data, int[][] weight) {for (int i = 0; i < verxs; i++) {graph.data[i] = data[i];for (int j = 0; j < verxs; j++) {graph.weight[i][j] = weight[i][j];}}}//显示图public void showGraph(MGraph graph) {for (int[] link : graph.weight) {System.out.println(Arrays.toString(link));}}//prim算法/*** @param mGraph 图* @param v 从图的第几个顶点开始生成*/public void prim(MGraph mGraph, int v) {//标记顶点是否被访问过,默认都为0,都没有被访问过int visited[] = new int[mGraph.verxs];//当前节点标记为已访问visited[v] = 1;//记录两个顶点的下标int h1 = -1;int h2 = -1;//当遇到比minWeight小的权值就会进行替换int minWeight= 10000;int sumWeight = 0;for (int k = 1; k < mGraph.verxs; k++) { //这里的mGraph.verxs的含义指的是应该循环的次数//因为有mGraph.verxs顶点,会有边数mGraph.verxs-1//要生成n-1条边,也就是要遍历n-1次,找到每一次权值最小的边// 每循环一次,visited[]中被遍历过的点都会改变,那么i和j能够遍历范围也会改变,找到每一次的最小权值的边//遍历子图for (int i = 0; i < mGraph.verxs; i++) { //结合条件visited[i] == 1 ,遍历所有节点中被访问过的节点for (int j = 0; j < mGraph.verxs; j++) { //结合条件visited[j] == 0 遍历所有节点中没有访问过的节点//同时这两个节点直接要连通,而且权值最小if (visited[i] == 1 && visited[j] == 0 && mGraph.weight[i][j] < minWeight) {//寻找已经访问过的节点和未访问过的节点,权值最小的边minWeight = mGraph.weight[i][j];h1 = i;h2 = j;}}}//退出一次,就找到了当下子图最小的权值边System.out.println("边<" + mGraph.data[h1] + "," + mGraph.data[h2] + "> 权值:" + minWeight);//将当前这个节点标记为被访问过的visited[h2] = 1;sumWeight += minWeight;//重置minWeight 重新寻找下一次子图minWeight = 10000;}System.out.println(sumWeight);}}
复制代码
class MGraph {int verxs; //表示图的节点个数char[] data; //存放节点数据int[][] weight; //存放边,邻接矩阵public MGraph(int verxs) {this.verxs = verxs;data = new char[verxs];weight = new int[verxs][verxs];}
}
复制代码
  • 调用
char[] data = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int verxs = data.length;
//描述邻接矩阵,10000表示不连通
int[][] weight = {// A B C D E F G{10000,5,7,10000,10000,10000,2}, //A{5,10000,10000,9,10000,10000,3}, //B{7,10000,10000,10000,8,10000,10000}, //C{10000,9,10000,10000,10000,4,10000}, //D{10000,10000,8,10000,10000,5,4}, //E{10000,10000,10000,4,5,10000,6}, //F{2,3,10000,10000,4,6,10000} //G
};
MGraph mGraph = new MGraph(verxs);
MinTree minTree = new MinTree();
minTree.createGraph(mGraph,verxs,data,weight);
minTree.showGraph(mGraph);
minTree.prim(mGraph,1);
复制代码

公交站问题(克鲁斯卡尔算法)

  • 生成最小生成树
  • 首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止
  • 核心是,将图中所有的边进行排序,为了能获取涵盖所有点的边,也就是说,其中的边不能有回路,因为有回路代表着,这两个点明明已经在这条路径上了,但是通过这条回路再次加入,这条边的加入就是浪费
class Krusal {private int edgeNum; //边的个数private char[] vertexs; //顶点数组private int[][] matrix; //邻接矩阵private static final int INF = Integer.MAX_VALUE;public Krusal(char[] vertexs, int[][] matrix) {//复制的方式,用深拷贝int vlen = vertexs.length;this.vertexs = new char[vlen];for (int i = 0; i < vlen; i++) {this.vertexs[i] = vertexs[i];}this.matrix = new int[vlen][vlen];for (int i = 0; i < vlen; i++) {for (int j = 0; j < vlen; j++) {this.matrix[i][j] = matrix[i][j];}}//统计边for (int i = 0; i < vlen; i++) {for (int j = i+1; j < vlen; j++) {if (this.matrix[i][j] != INF) {edgeNum ++;}}}}//算法//要保证每一次加入的边都有新的点,那么p1的最终点和p2的最终点都不能相同public void krusalAlgorithm() {int index = 0; //表示最后结果的数组索引int[] ends = new int[vertexs.length]; //保存已有最小生成树,每个边的顶点的终点,初始为0,0,0,0,0,0,0//结果数组EData[] res = new EData[edgeNum];//获取图中所有边得集合EData[] edges = getEdges();//排序,针对边的算法sortEdges(edges);//遍历所有的边,将边添加到最小生成树中,判断准备加入的边是否形成回路,如果没有,就加入,否则不能加入for (int i = 0; i < edgeNum; i++) {//获取到第i条边的第一个顶点int p1 = getPosition(edges[i].start);//获取另一个顶点int p2 = getPosition(edges[i].end);//获取p1这个顶点在最小生成树的终点int m = getEnd(ends, p1); //如果没有加入则返回自己本身,那么此时m=p1//获取p2这个顶点在最小生成树的终点int n = getEnd(ends, p2);//那么对于最开始的时候 m=p1 n=p2 也就是设置p1的终点是p2//如果此时p1 p2的终点不是一个,说明这个新的边可以加入这个路径中,并且将p1的终点m指向p2的终点n//由于`getEnd`的方法中,采取while方式寻找,就可以找到p1出发的终点的终点,//那么如果此时待加入的路径是p1和p2的终点n,由于n此时未加入,终点就是本身//为通过`getEnd`,通过找到p1的终点找到p1终点的终点是n,//那么说明这几个点都在一个路径上,如果加入就不是新的点了,产生回路 //是否构成回路if (m != n) {//设置m的终点是n,如果不构成回路,那么ends[m] = n;res[index++] = edges[i]; //这条边可以加入}}//统计并打印最小生成树//输入n-1条边,最后一条的下标是vertexs.length-2,但是切割的时候是[),左闭右开Spliterator<EData> spliterator = Arrays.spliterator(res, 0, vertexs.length-1);spliterator.forEachRemaining(eData -> System.out.println(eData));}public void print() {for (int i = 0; i < vertexs.length; i++) {for (int j = 0; j < vertexs.length; j++) {System.out.printf("%13d", matrix[i][j]);}System.out.println();}}//对边进行排序public void sortEdges(EData[] edges) {//冒泡//不断确定倒数第i个大的数,只需要确定edges.length-1个数即可//前一个和后一个进行比,比到最后的时候 最大的下标为j+1 也就是edges.length - 1 + 1 - ifor (int i = 0; i < edges.length - 1; i++) {for (int j = 0; j < edges.length - 1 - i; j++) {if (edges[j].weight > edges[j+1].weight) {EData temp = edges[j];edges[j] = edges[j+1];edges[j+1] = temp;}}}}//输入顶点值,返回顶点对应的下标public int getPosition(char ch) {for (int i = 0; i < vertexs.length; i++) {if (vertexs[i] == ch) {return i;}}return -1;}//获取图中的边,放入EDatapublic EData[] getEdges() {int index = 0;EData[] edges = new EData[edgeNum];for (int i = 0; i < vertexs.length; i++) {for (int j = i + 1; j < vertexs.length; j++) { //跳过自己,并且不会重复 例如i=0 扫描 j=1,2,3,4,5,6;i=1 扫描j=2,3,4,5,6if (matrix[i][j] != INF) {edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);}}}return edges;}//获取下标为i的顶点的终点,用于判断两个顶点的终点是否相同//数组记录了各个顶点对应的重点,且在遍历过程中逐步形成的,会不断变化//返回下标为i的终点下标public int getEnd(int[] ends, int i) {while (ends[i] != 0) {i = ends[i];}return i;}
}复制代码
  • 边类
class EData {char start; //起点char end; //终点int weight; //权值public EData(char start, char end, int weight) {this.start = start;this.end = end;this.weight = weight;}@Overridepublic String toString() {return "EData{" +"start=" + start +", end=" + end +", weight=" + weight +'}';}
}
复制代码
  • 调用
char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int[][] matrix = { //0表示自己// A B C D E F G{0,12,INF,INF,INF,16,14},{12,0,10,INF,INF,7,INF},{INF,10,0,3,5,6,INF},{INF,INF,3,0,4,INF,INF},{INF,INF,5,4,0,2,8},{16,7,6,INF,INF,16,14},{14,INF,INF,INF,8,9,0}
};
Krusal krusal = new Krusal(vertexs, matrix);
krusal.krusalAlgorithm();
复制代码

小结

  • 普里姆和克鲁斯卡尔都是求最小生成树的算法,按我的理解本上是求最小路径,也就是说这条路径要经过所有的点,而且路径上的权重和最小。那么这样就需要注意到,在遍历所有点或者边时,加过的点不能重复加,并且加进去的边要是最小的,而以上两个算法就是针对于不同的角度
  • 而这两个算法则有一个默认的大前提每次往路径里添加的点都要是新的点
  • 普里姆算法,针对的是顶点,将所有顶点分成两个部分,一个部分是已经在这个路径里的点(遍历过的点),一个是不在这个路径上的点(没有遍历过的点),所有才会有了两层for循环,保证了上面的大前提,每次发生关系的必定是遍历过的点和没有遍历过的点,检索所有遍历过的点与没有遍历过的点之间的权重值,并找出最小的那个权重值,再将新的点加入遍历过的集合中,由于图中的点必定是有邻接点的,所以也就有了一个隐藏的条件,n个点只需要n-1条边就能将其连接,那么也只需要整体遍历n-1次,就可以将所有的点都遍历到了,这也就是最外面的for循环次数的由来
  • 克鲁斯卡尔算法,针对的是边,做了很多的铺垫工作,最重要的则是将所有边取出并排序,所以在这个铺垫工作中,最好将其看做有向图不要取重复的边,通过每次一次都尝试加入最短的边,但是也要保证一个大前提新加入的点要是全新的点,所以其就通过记录终点的方式,这里就有了三种情况

一种是一条全新的边,跟原来的边完全没有关系,那么这条边的两个顶点就是其本身,但是一端必定指向另一段(ends[m]=n,这里的m和n分别是p1、p2)

第二种情况,这条边上的一个点已经被包含在这个路径中了,那么如果一个顶点是全新的点,那通过end[i]只能找到本身,就也可以添加,因为一个全新的点是不可能出现在ends数组中的,那么新的点的终点就将指向这个路径的终点,那么这句话是什么意思呢,请看第三种情况的解释

第三种情况,这条边上的两个点都已经被包含在这个路径上了,那么这里就要解释ends数组添加终点的特性。查询ends数组时,会按照添加顺序依次查询到这条路径最终的终点,也就是说只要这个点已经被添加到这条路径上后,这个点的终点和这条路径的其他终点都会是一样的,举例说明,路径中包含A-B 和 B-C,此时添加A-C,很显然根据添加顺序ends[A]=B, ends[B]=C,在查询的时候通过while循环,当查询A终点的时候,返回的就是C,而且只要在B-C之前添加,返回的终点都会是C,比如A-B E-B B-C,查询E终点时,显然已经仍然会是C,这也就说明了,当两个点都被包含在这个路径时,这两个点的终点都会是最后一次被成功添加的边的终点。而对应的,第二种情况中,在检测一个全新的点时,是无法返回出这个路径的终点的,只能返回本身

但是,这里就会有一个疑问,如果终点是0呢,也就是说所有的点的终点都是A,但是这在一开始就做了限制,在获取所有边的时候,规定小下标指向大下标,整个算法也是基于这个规则下来写的,因为最终的目标时找出这么多条边组合出的最短路径,并且这个路径可以涵盖所有的点

作者:99永远差一分
链接:https://juejin.cn/post/7006653609454600206
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

JAVA-数据结构与算法-修路问题(普里姆算法)和公交站问题(克鲁斯卡尔算法)相关推荐

  1. JavaScript实现prime(普里姆)算法和kruskal(克鲁斯卡尔)算法

    prime算法 prime算法生成最小生成树(Minimum Cost Spanning Tree)的一种算法. prime算法是从指定的顶点开始构建最小生成树. prime算法思想如下: 从连通图( ...

  2. 【HDU - 1301】Jungle Roads(并查集+最小生成树)(内附最小生成树两种算法 克鲁斯特尔算法amp;amp;普里姆算法)

    题干: Jungle Roads Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  3. 第六章图-算法6.8普里姆算法

    第六章图-算法6.8普里姆算法 代码实现 #pragma once #include <iostream>using namespace std;//图的邻接矩阵存储(创建无向图) //表 ...

  4. (数据结构)图的最小生成树 普里姆算法(Prim)

    假设要在n个城市之间建立通信联络网,每两个城市之间建立线路都需要花费不同大小的经费,则连通n个城市只需要n-1个条线路,最小生成树解决的问题就是:如何在最节省经费的前提下建立这个通信网 也可以理解为: ...

  5. 【数据结构与算法】普里姆算法的介绍和修路问题程序实现

    目录 1. 最小生成树的介绍 2. 普里姆算法的介绍 3. 修路问题的介绍 1. 最小生成树的介绍 最小生成树(Minimum Cost Spanning Tree),简称MST.给定一个带权的无向连 ...

  6. Java用普里姆算法(prim)解决修路最短路径问题

    14.6 普里姆算法 14.6.1 应用场景-修路问题 看一个应用场景和问题: 有胜利乡有 7 个村庄(A, B, C, D, E, F, G) ,现在需要修路把 7 个村庄连通 各个村庄的距离用边线 ...

  7. 普里姆算法解决修路问题

    一 问题提出 1 胜利乡有7个村庄(A, B, C, D, E, F, G),现在需要修路把7个村庄连通. 2 各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里. 问:如何修路保证各个 ...

  8. 普里姆(Prim)算法(P算法):修路问题

    1,应用场景-修路问题 如图,此时有7个村庄['A', 'B', 'C', 'D', 'E', 'F', 'G'],现在需要把这7个村庄连通 村庄之间的连接线表示可能修路的图示,权值表示举例 此时,如 ...

  9. 数据结构与算法|最小生成树算法(普里姆算法、克鲁斯卡尔算法)

    最小生成树算法 C语言代码部分来自小甲鱼的<数据结构与算法> 文章目录 最小生成树算法 一.普里姆(Prim)算法 1.C语言代码 2.算法思路 二.克鲁斯卡尔(Kruskal)算法 1. ...

  10. 数据结构实验-图-普里姆算法、克鲁斯科尔算法

    数据结构实验-图-普里姆算法.克鲁斯科尔算法 (实验)自定义存储结构,并设计程序完成如下功能: ①创建图:创建带权无向图. ②普里姆算法:采用普里姆算法依次输出最小生成树中各条边. ③克鲁斯科尔算法: ...

最新文章

  1. 关于批量修改AD域用户的脚本
  2. 浅谈JS原型与原型链(一)
  3. 外中断---汇编学习笔记
  4. 因为在此系统上禁止运行脚本。有关详细信息_在弃用11年后微软终于允许IT管理员禁用IE中的JScript脚本引擎...
  5. php发送gmail,使用GMail SMTP服务器从PHP页面发送电子邮件
  6. EasyRTMP CPU占用问题调优(一)
  7. 如何处理APF框架的错误消息:Filter is too complex error
  8. 如何在vue中使用图形验证码
  9. LaTeX的安装教程及问题记录
  10. 内蒙古工业大学计算机科学与技术,计算机科学与技术的应用领域简述论文内蒙古工业大学.doc...
  11. JSP笔记——7.自定义标签
  12. 传导EMI抑制-π型滤波器设计
  13. 《utils》yaml,yml格式化
  14. 一小时快速建立数据分析平台
  15. 【C语言】斐波那契数列,依次输出1 1 2 3 5 13等前10个数
  16. 工具使用 - IDA使用
  17. 台式机和笔记本属于什么计算机,pc机属于什么型计算机
  18. php 事件,php的事件处理机制(回调函数)
  19. 基于STM32的超声波雷达项目【可拟合构建平面地图】(代码开源)
  20. microstation level3 10 elliptical cone solid 、ellipsoid、polyhedron

热门文章

  1. c语言程序设计拉丁方阵结构图,C语言程序设计100例之(29):拉丁方阵
  2. 中国有句俗语叫“三天打鱼两天晒网”。某人从2010年1月1日起开始“三天打鱼两天晒网”,问这个人在以后的某一天中是“打鱼”还是“晒网”。用java实现程序解决问题。
  3. TOFEL托福经验贴
  4. 小白安装linux系统-u盘安装lubuntu
  5. HNUST OJ 2106 普通电梯
  6. ESP8266/ESP32 基础篇: 时间同步 SNTP 介绍和使用
  7. 电子科技大学和东北大学计算机专业哪个好,2016东北大学VS电子科技大学 谁执牛耳?...
  8. 姚洋:建议国家购买三四线空置房 再低价出售给进城农民
  9. Java将对象的属性值合并
  10. 【数学建模】基于matlab模拟疫情SEIRS模型【含Matlab源码 2214期】