2023年8月1日发(作者:)

克鲁斯卡尔算法(Kruskal)详解应⽤场景-公交站问题看⼀个应⽤场景和问题:1) 某城市新增 7 个站点 (A, B, C, D, E, F, G) ,现在需要修路把 7 个站点连通2) 各个站点的距离⽤边线表⽰ ( 权 ) ,⽐如 A – B 距离 12 公⾥3) 问:如何修路保证各个站点都能连通,并且总的修建公路总⾥程最短 ?

克鲁斯卡尔算法介绍1) 克鲁斯卡尔 (Kruskal) 算法,是⽤来求加权连通图的最⼩⽣成树的算法 。2) 基本思想 :按照权值从⼩到⼤的顺序选择 n-1 条边,并保证这 n-1 条边不构成回路3) 具体做法 :⾸先构造⼀个只含 n 个顶点的森林,然后依权值从⼩到⼤从连通⽹中选择边加⼊到森林中,并使森林中不产⽣回路,直⾄森林变成⼀棵树为⽌

克鲁斯卡尔算法图解说明以城市公交站问题来图解说明 克鲁斯卡尔算法的原理和步骤:在含有n个顶点的连通图中选择n-1条边,构成⼀棵极⼩连通⼦图,并使该连通⼦图中n-1条边上权值之和达到最⼩,则称其为连通⽹的最⼩⽣成树。

例如,对于如上图G4所⽰的连通⽹可以有多棵权值总和不相同的⽣成树。

克鲁斯卡尔算法图解以上图G4为例,来对克鲁斯卡尔进⾏演⽰(假设,⽤数组R保存最⼩⽣成树结果)。

第1步:将边加⼊R中。

的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第2步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第3步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第4步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,但会和已有的边构成回路;因此,跳过边。同理,跳过边。将边加⼊到最⼩⽣成树结果R中。

第5步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第6步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,但会和已有的边构成回路;因此,跳过边。同理,跳过边。将边加⼊到最⼩⽣成树结果R中。此时,最⼩⽣成树构造完成!它包括的边依次是: 。克鲁斯卡尔算法分析根据前⾯介绍的克鲁斯卡尔算法的基本思想和做法,我们能够了解到,克鲁斯卡尔算法重点需要解决的以下两个问题:

问题⼀ 对图的所有边按照权值⼤⼩进⾏排序。

问题⼆ 将边添加到最⼩⽣成树中时,怎么样判断是否形成了回路。问题⼀很好解决,采⽤排序算法进⾏排序即可。问题⼆,处理⽅式是:记录顶点在"最⼩⽣成树"中的终点,顶点的终点是"在最⼩⽣成树中与它连通的最⼤顶点"。然后每次需要将⼀条边添加到最⼩⽣存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。

如何判断是否构成回路-举例说明(如图)

在将 加⼊到最⼩⽣成树R中之后,这⼏条边的顶点就都有了终点:(01) C的终点是F。

(02) D的终点是F。

(03) E的终点是F。

(04) F的终点是F。

关于终点的说明:1. 就是将所有顶点按照从⼩到⼤的顺序排列好之后;某个顶点的终点就是"与它连通的最⼤顶点"。2. 因此,接下来,虽然是权值最⼩的边。但是C和E的终点都是F,即它们的终点相同,因此,将加⼊最⼩⽣成树的话,会形成回路。这就是判断回路的⽅式。也就是说,我们加⼊的边的两个顶点不能都指向同⼀个终点,否则将构成回路。【后⾯有代码说明】克鲁斯卡尔算法的代码说明package l;import ;public class KruskalCase { private int edgeNum; //边的个数 private char[] vertexs; //顶点数组 private int[][] matrix; //邻接矩阵 //使⽤ INF 表⽰两个顶点不能连通 private static final int INF = _VALUE; public static void main(String[] args) { char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'}; //克鲁斯卡尔算法的邻接矩阵

int matrix[][] = { /*A*//*B*//*C*//*D*//*E*//*F*//*G*/ /*A*/ { 0, 12, INF, INF, INF, 16, 14}, /*B*/ { 12, 0, 10, INF, INF, 7, INF}, /*C*/ { INF, 10, 0, 3, 5, 6, INF}, /*D*/ { INF, INF, 3, 0, 4, INF, INF}, /*E*/ { INF, INF, 5, 4, 0, 2, 8}, /*F*/ { 16, 7, 6, INF, 2, 0, 9}, /*G*/ { 14, INF, INF, INF, 8, 9, 0}};

//⼤家可以在去测试其它的邻接矩阵,结果都可以得到最⼩⽣成树.

//创建KruskalCase 对象实例 KruskalCase kruskalCase = new KruskalCase(vertexs, matrix); //输出构建的 (); l(); } public KruskalCase(char[] vertexs, int[][] matrix) { // TODO Auto-generated constructor stub //初始化顶点数和边的个数 int vlen = ;

//初始化顶点, 复制拷贝的⽅式 s = new char[vlen]; for(int i = 0; i < ; i++) { s[i] = vertexs[i]; }

//初始化边, 使⽤的是复制拷贝的⽅式 = new int[vlen][vlen]; for(int i = 0; i < vlen; i++) { for(int j= 0; j < vlen; j++) { [i][j] = matrix[i][j]; } } //统计边的条数 for(int i =0; i < vlen; i++) { for(int j = i+1; j < vlen; j++) { if([i][j] != INF) { edgeNum++; } } } } private void kruskal() { // TODO Auto-generated method stub int index=0;//表⽰最后结果数组的索引 int ends[]=new int[edgeNum];//⽤于保存"已有最⼩⽣成树" 中的每个顶点在最⼩⽣成树中的终点 //创建结果数组, 保存最后的最⼩⽣成树 Edata[] result=new Edata[edgeNum];

//获取图中 所有的边的集合 , ⼀共有12边 Edata[] edges = getEdges(); n("图的边的集合=" + ng(edges) + " 共"+ ); //12 //按照边的权值⼤⼩进⾏排序(从⼩到⼤) (edges);

//遍历edges 数组,将边添加到最⼩⽣成树中时,判断是准备加⼊的边否形成了回路,如果没有,就加⼊ rets, 否则不能加⼊ for(int i=0;i

//获取p1这个顶点在已有最⼩⽣成树中的终点

int n=getEnd(ends,p1);//m = 4 //获取p2这个顶点在已有最⼩⽣成树中的终点 int m=getEnd(ends, p2);// n = 5 //是否构成回路 if(n!=m) {//没有构成回路 ends[n]=m; // 设置m 在"已有最⼩⽣成树"中的终点 [0,0,0,0,5,0,0,0,0,0,0,0]

result[index++]=edges[i];//有⼀条边加⼊到rets数组 } } // 。 //统计并打印 "最⼩⽣成树", 输出 rets n("最⼩⽣成树为"); for(int i = 0; i < index; i++) { n(result[i]); } } /** * 功能: 获取下标为i的顶点的终点(), ⽤于后⾯判断两个顶点的终点是否相同 * @param ends : 数组就是记录了各个顶点对应的终点是哪个,ends 数组是在遍历过程中,逐步形成 * @param i : 表⽰传⼊的顶点对应的下标 * @return 返回的就是 下标为i的这个顶点对应的终点的下标, ⼀会回头还有来理解 */ private int getEnd(int[] ends, int p1) {// i = 4 [0,0,0,0,5,0,0,0,0,0,0,0] // TODO Auto-generated method stub while(ends[p1]!=0) { p1=ends[p1]; } return p1; } /** *

* @param ch 顶点的值,⽐如'A','B' * @return 返回ch顶点对应的下标,如果找不到,返回-1 */ private int getPosition(char ch) { for(int i = 0; i < ; i++) { if(vertexs[i] == ch) {//找到 return i; } } //找不到,返回-1 return -1; } /** * 功能: 获取图中边,放到EData[] 数组中,后⾯我们需要遍历该数组 * 是通过matrix 邻接矩阵来获取 * EData[] 形式 [['A','B', 12], ['B','F',7], .....] * @return */ private Edata[] getEdges() { // TODO Auto-generated method stub int index = 0; Edata[] edges = new Edata[edgeNum]; for(int i=0;i<;i++) { for(int j=i+1;j<;j++) { if(matrix[i][j]!=INF) { edges[index++] = new Edata(vertexs[i], vertexs[j], matrix[i][j]); } } } } return edges; } //打印邻接矩阵 private void print() { // TODO Auto-generated method stub n("邻接矩阵为: n"); for(int i = 0; i < ; i++) { for(int j=0; j < ; j++) { ("%12d", matrix[i][j]); } n();//换⾏ } }

}//创建⼀个类EData ,它的对象实例就表⽰⼀条边class Edata implements Comparable{ char start;//边的⼀个点 char end;//边的另外⼀个点 int weight;//边的权值 public Edata(char start, char end, int weight) { super(); = start; = end; = weight; } @Override public String toString() { return "Edate [start=" + start + ", end=" + end + ", weight=" + weight + "]"; } @Override public int compareTo(Edata o) { // TODO Auto-generated method stub return ; }

}

2023年8月1日发(作者:)

克鲁斯卡尔算法(Kruskal)详解应⽤场景-公交站问题看⼀个应⽤场景和问题:1) 某城市新增 7 个站点 (A, B, C, D, E, F, G) ,现在需要修路把 7 个站点连通2) 各个站点的距离⽤边线表⽰ ( 权 ) ,⽐如 A – B 距离 12 公⾥3) 问:如何修路保证各个站点都能连通,并且总的修建公路总⾥程最短 ?

克鲁斯卡尔算法介绍1) 克鲁斯卡尔 (Kruskal) 算法,是⽤来求加权连通图的最⼩⽣成树的算法 。2) 基本思想 :按照权值从⼩到⼤的顺序选择 n-1 条边,并保证这 n-1 条边不构成回路3) 具体做法 :⾸先构造⼀个只含 n 个顶点的森林,然后依权值从⼩到⼤从连通⽹中选择边加⼊到森林中,并使森林中不产⽣回路,直⾄森林变成⼀棵树为⽌

克鲁斯卡尔算法图解说明以城市公交站问题来图解说明 克鲁斯卡尔算法的原理和步骤:在含有n个顶点的连通图中选择n-1条边,构成⼀棵极⼩连通⼦图,并使该连通⼦图中n-1条边上权值之和达到最⼩,则称其为连通⽹的最⼩⽣成树。

例如,对于如上图G4所⽰的连通⽹可以有多棵权值总和不相同的⽣成树。

克鲁斯卡尔算法图解以上图G4为例,来对克鲁斯卡尔进⾏演⽰(假设,⽤数组R保存最⼩⽣成树结果)。

第1步:将边加⼊R中。

的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第2步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第3步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第4步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,但会和已有的边构成回路;因此,跳过边。同理,跳过边。将边加⼊到最⼩⽣成树结果R中。

第5步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,因此将它加⼊到最⼩⽣成树结果R中。

第6步:将边加⼊R中。

上⼀步操作之后,边的权值最⼩,但会和已有的边构成回路;因此,跳过边。同理,跳过边。将边加⼊到最⼩⽣成树结果R中。此时,最⼩⽣成树构造完成!它包括的边依次是: 。克鲁斯卡尔算法分析根据前⾯介绍的克鲁斯卡尔算法的基本思想和做法,我们能够了解到,克鲁斯卡尔算法重点需要解决的以下两个问题:

问题⼀ 对图的所有边按照权值⼤⼩进⾏排序。

问题⼆ 将边添加到最⼩⽣成树中时,怎么样判断是否形成了回路。问题⼀很好解决,采⽤排序算法进⾏排序即可。问题⼆,处理⽅式是:记录顶点在"最⼩⽣成树"中的终点,顶点的终点是"在最⼩⽣成树中与它连通的最⼤顶点"。然后每次需要将⼀条边添加到最⼩⽣存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。

如何判断是否构成回路-举例说明(如图)

在将 加⼊到最⼩⽣成树R中之后,这⼏条边的顶点就都有了终点:(01) C的终点是F。

(02) D的终点是F。

(03) E的终点是F。

(04) F的终点是F。

关于终点的说明:1. 就是将所有顶点按照从⼩到⼤的顺序排列好之后;某个顶点的终点就是"与它连通的最⼤顶点"。2. 因此,接下来,虽然是权值最⼩的边。但是C和E的终点都是F,即它们的终点相同,因此,将加⼊最⼩⽣成树的话,会形成回路。这就是判断回路的⽅式。也就是说,我们加⼊的边的两个顶点不能都指向同⼀个终点,否则将构成回路。【后⾯有代码说明】克鲁斯卡尔算法的代码说明package l;import ;public class KruskalCase { private int edgeNum; //边的个数 private char[] vertexs; //顶点数组 private int[][] matrix; //邻接矩阵 //使⽤ INF 表⽰两个顶点不能连通 private static final int INF = _VALUE; public static void main(String[] args) { char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'}; //克鲁斯卡尔算法的邻接矩阵

int matrix[][] = { /*A*//*B*//*C*//*D*//*E*//*F*//*G*/ /*A*/ { 0, 12, INF, INF, INF, 16, 14}, /*B*/ { 12, 0, 10, INF, INF, 7, INF}, /*C*/ { INF, 10, 0, 3, 5, 6, INF}, /*D*/ { INF, INF, 3, 0, 4, INF, INF}, /*E*/ { INF, INF, 5, 4, 0, 2, 8}, /*F*/ { 16, 7, 6, INF, 2, 0, 9}, /*G*/ { 14, INF, INF, INF, 8, 9, 0}};

//⼤家可以在去测试其它的邻接矩阵,结果都可以得到最⼩⽣成树.

//创建KruskalCase 对象实例 KruskalCase kruskalCase = new KruskalCase(vertexs, matrix); //输出构建的 (); l(); } public KruskalCase(char[] vertexs, int[][] matrix) { // TODO Auto-generated constructor stub //初始化顶点数和边的个数 int vlen = ;

//初始化顶点, 复制拷贝的⽅式 s = new char[vlen]; for(int i = 0; i < ; i++) { s[i] = vertexs[i]; }

//初始化边, 使⽤的是复制拷贝的⽅式 = new int[vlen][vlen]; for(int i = 0; i < vlen; i++) { for(int j= 0; j < vlen; j++) { [i][j] = matrix[i][j]; } } //统计边的条数 for(int i =0; i < vlen; i++) { for(int j = i+1; j < vlen; j++) { if([i][j] != INF) { edgeNum++; } } } } private void kruskal() { // TODO Auto-generated method stub int index=0;//表⽰最后结果数组的索引 int ends[]=new int[edgeNum];//⽤于保存"已有最⼩⽣成树" 中的每个顶点在最⼩⽣成树中的终点 //创建结果数组, 保存最后的最⼩⽣成树 Edata[] result=new Edata[edgeNum];

//获取图中 所有的边的集合 , ⼀共有12边 Edata[] edges = getEdges(); n("图的边的集合=" + ng(edges) + " 共"+ ); //12 //按照边的权值⼤⼩进⾏排序(从⼩到⼤) (edges);

//遍历edges 数组,将边添加到最⼩⽣成树中时,判断是准备加⼊的边否形成了回路,如果没有,就加⼊ rets, 否则不能加⼊ for(int i=0;i

//获取p1这个顶点在已有最⼩⽣成树中的终点

int n=getEnd(ends,p1);//m = 4 //获取p2这个顶点在已有最⼩⽣成树中的终点 int m=getEnd(ends, p2);// n = 5 //是否构成回路 if(n!=m) {//没有构成回路 ends[n]=m; // 设置m 在"已有最⼩⽣成树"中的终点 [0,0,0,0,5,0,0,0,0,0,0,0]

result[index++]=edges[i];//有⼀条边加⼊到rets数组 } } // 。 //统计并打印 "最⼩⽣成树", 输出 rets n("最⼩⽣成树为"); for(int i = 0; i < index; i++) { n(result[i]); } } /** * 功能: 获取下标为i的顶点的终点(), ⽤于后⾯判断两个顶点的终点是否相同 * @param ends : 数组就是记录了各个顶点对应的终点是哪个,ends 数组是在遍历过程中,逐步形成 * @param i : 表⽰传⼊的顶点对应的下标 * @return 返回的就是 下标为i的这个顶点对应的终点的下标, ⼀会回头还有来理解 */ private int getEnd(int[] ends, int p1) {// i = 4 [0,0,0,0,5,0,0,0,0,0,0,0] // TODO Auto-generated method stub while(ends[p1]!=0) { p1=ends[p1]; } return p1; } /** *

* @param ch 顶点的值,⽐如'A','B' * @return 返回ch顶点对应的下标,如果找不到,返回-1 */ private int getPosition(char ch) { for(int i = 0; i < ; i++) { if(vertexs[i] == ch) {//找到 return i; } } //找不到,返回-1 return -1; } /** * 功能: 获取图中边,放到EData[] 数组中,后⾯我们需要遍历该数组 * 是通过matrix 邻接矩阵来获取 * EData[] 形式 [['A','B', 12], ['B','F',7], .....] * @return */ private Edata[] getEdges() { // TODO Auto-generated method stub int index = 0; Edata[] edges = new Edata[edgeNum]; for(int i=0;i<;i++) { for(int j=i+1;j<;j++) { if(matrix[i][j]!=INF) { edges[index++] = new Edata(vertexs[i], vertexs[j], matrix[i][j]); } } } } return edges; } //打印邻接矩阵 private void print() { // TODO Auto-generated method stub n("邻接矩阵为: n"); for(int i = 0; i < ; i++) { for(int j=0; j < ; j++) { ("%12d", matrix[i][j]); } n();//换⾏ } }

}//创建⼀个类EData ,它的对象实例就表⽰⼀条边class Edata implements Comparable{ char start;//边的⼀个点 char end;//边的另外⼀个点 int weight;//边的权值 public Edata(char start, char end, int weight) { super(); = start; = end; = weight; } @Override public String toString() { return "Edate [start=" + start + ", end=" + end + ", weight=" + weight + "]"; } @Override public int compareTo(Edata o) { // TODO Auto-generated method stub return ; }

}