克鲁斯卡尔算法(Kruskal算法)与最小生成树问题
@author:QYX
开源结束,我回来了!
基本思想:(1)构造一个只含n个顶点,边集为空的子图。若将图中各个顶点看成一棵树的根节点,则它是一个含有n棵树的森林。(2)从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图。也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之(3)依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
简单来说:(1)将图中的所有边都去掉。(2)将边按权值从小到大的顺序添加到图中,保证添加的过程中不会形成环(3)重复上一步直到连接所有顶点,此时就生成了最小生成树。这是一种贪心策略。
问题:判断某条边<u, v>的加入是否会在已经选定的边集集合中形成环。
解决方法:使用并查集,分别找出两个顶点u, v所在树的根节点。若根节点相同,说明u, v在同一棵树中,则u, v连接起来会形成环;若根节点不同,则u, v不在一棵树中,连接起来不会形成环,而是将两棵树合并
package com.qyx.krusal; import java.util.Arrays; import javax.swing.plaf.basic.BasicBorders.MarginBorder; public class KrusalCase { private int edgeNum;//边的个数 private char[] vertexs;//顶点数组 private int[][] matrix;//邻接矩阵 private static final int INF=Integer.MAX_VALUE;//使用INF表示两个顶点不能连通 public KrusalCase(char[] vertexs, int[][] matrix) { // super(); // this.edgeNum = edgeNum; // this.vertexs = vertexs; // this.matrix = matrix; //初始化顶点数和边的个数 int vlen=vertexs.length; this.vertexs=new char[vlen]; for(int i=0;i<vertexs.length;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) { this.edgeNum++; } } } } //打印邻接矩阵 public void print() { System.out.printf("邻接矩阵为:\n"); for(int i=0;i<vertexs.length;i++) { for(int j=0;j<vertexs.length;j++) { System.out.printf("%12d\t",matrix[i][j]); } System.out.printf("\n"); } } public static void main(String[] args) { char[] vertexs={\'A\',\'B\',\'C\',\'D\',\'E\',\'F\',\'G\'}; //创建克鲁斯卡尔的邻接矩阵 int matrix[][]={ /*A*//*B*//*C*//*D*//*E*//*F*//*G*/ {0,12,INF,INF,INF,16,14}, /*A*/ {12,0,10,INF,INF,7,INF}, /*B*/ {INF,10,0,3,5,6,INF}, /*C*/ {INF,INF,3,0,4,INF,INF}, /*D*/ {INF,INF,5,4,0,2,8}, /*E*/ {16,7,6,INF,2,0,9}, /*F*/ {14,INF,INF,INF,8,9,0}, /*G*/ }; KrusalCase kcase=new KrusalCase(vertexs, matrix); kcase.print(); kcase.kruskal(); } //对边进行排序,使用冒泡 /** * 功能:使用冒泡排序对边进行排序 * @param edges 边的集合 */ private void sortEdges(EData[] edges) { for(int i=0;i<edges.length-1;i++) { for(int j=0;j<edges.length-1-i;j++) { //这里可以让边实现Comparable<T>接口 if(edges[j].compareTo(edges[j+1])>0) { EData temp=edges[j]; edges[j]=edges[j+1]; edges[j+1]=temp; } } } } /** * * @param ch 顶点的值,比如\'A\',\'B\' * @return 返回ch顶点对应的下标,如果找不到,返回-1 */ private int getPosition(char ch) { for(int i=0;i<vertexs.length;i++) { if(vertexs[i]==ch) { return i; } } return -1; } /** * 功能获取图中的边,放到EDate[]数组中,后面我们需要遍历该数组 * @return 返回边的数组 * 通过matrix邻接矩阵来获取 */ private EData[] getEdges() { int index=0; EData[] datas=new EData[edgeNum]; for(int i=0;i<vertexs.length;i++) { for(int j=i+1;j<vertexs.length;j++) { if(matrix[i][j]!=INF) { //遍历左三角,因为右三角和左三角对称,而且对角线全为0,所以抛弃对角线取一半的不为INF的数据就行 datas[index++]=new EData(vertexs[i], vertexs[j], matrix[i][j]); } } } return datas; } /** * 功能:获取下标为i的顶点的终点,用于后面判断两个顶点的终点是否相同 * @param ends 该数组记录了各个顶点对应的终点是哪个,ends数组是在遍历过程中逐步形成的 * @param i 表示传入的顶点对应的下标 * @return 返回的就是下标为i的这个顶点的终点的下标 */ private int getEnd(int[] ends,int i) { while(ends[i]!=0) { i=ends[i]; } return i; } public void kruskal() { int index=0;//表示最后结果数组的索引 int[] ends=new int[edgeNum];//用于保存"已有最小生成树"中的每个顶点在最小生成树中的终点 //创建结果数组,保存最后的最小生成树 EData[] rets=new EData[edgeNum]; //获取图中所有的边的集合,一共有12条边 EData[] edges=getEdges(); //按照边的权值从小到大排序 sortEdges(edges); //遍历edges,将边添加到最小生成树中时,判断是准备加入的边否形成了回路,如果没有,就加入rets,否则不能加入 for(int i=0;i<edgeNum;i++) { //获取到第i条边的第一个顶点 int p1=getPosition(edges[i].getStart()); //获取到第i条边的第二个顶点 int p2=getPosition(edges[i].getEnd()); //获取p1这个顶点在已有的最小生成树中的终点 int m=getEnd(ends, p1); //获取p2这个顶点在已有的最小生成树中的终点 int n=getEnd(ends, p2); //判断是否构成回路 if(m!=n) { ends[m]=n; rets[index++]=edges[i]; } } //统计并打印最小生成树,输出rets System.out.println("最小生成树为"+Arrays.toString(rets)); } } //创建一个类,它的对象实例就表示一条边 class EData<T> implements Comparable<T>{ private char start; //边的一个点 private char end; //边的另一个店 private int weight; //边的权值 public char getStart() { return start; } public void setStart(char start) { this.start = start; } public char getEnd() { return end; } public void setEnd(char end) { this.end = end; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } //构造器 public EData(char start, char end, int weight) { super(); this.start = start; this.end = end; this.weight = weight; } //重写toString方法,便于输出边 @Override public String toString() { return "EData [start=" + start + ", end=" + end + ", weight=" + weight + "]"; } @Override public int compareTo(T o) { if(((EData)o).getWeight()>this.getWeight()) { return -1; } return 1; } }