普里姆算法介绍
普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图。
代码实现:
import java.util.Arrays; public class PrimAlgorithm { public static void main(String[] args) { char[] data = {'A','B','C','D','E','F','G'}; int numNode= data.length; int[][] weight={ {10000,5,7,10000,10000,10000,2}, {5,10000,10000,9,10000,10000,3}, {7,10000,10000,10000,8,10000,10000}, {10000,9,10000,10000,10000,4,10000}, {10000,10000,8,10000,10000,5,4}, {10000,10000,10000,4,5,10000,6}, {2,3,10000,10000,4,6,10000} }; //创建一个MGraph对象 MGraph graph=new MGraph(numNode); //创建一个MinTree对象 MinTree minTree=new MinTree(); //创建图 minTree.createGraph(graph,numNode,data,weight); //显示图的邻接矩阵 minTree.showGraph(graph); //测试prim算法 minTree.prim(graph,0); } } //创建最小生成树 class MinTree{ /** * 创建图 * @param graph 图对象 * @param numNode 图的顶点个数 * @param data 图的各顶点值 * @param weight 图邻接矩阵 */ public void createGraph(MGraph graph,int numNode,char[] data,int[][] weight){ for (int i = 0; i < numNode; i++) { graph.data[i]=data[i]; for (int j = 0; j < numNode; 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 graph 传入的图 * @param v 表示从图的第几个顶点开始生成 'A' -> 0 'B' -> 1 */ public void prim(MGraph graph,int v){ //visited[]数组标记顶点是否被访问过,初始化为0,默认元素都没有被访问过 int[] visited=new int[graph.numNode]; visited[v]=1; int maxWeight=10000; //用h1和h2记录两个定点的下标 int h1=-1; int h2=-1; for (int k = 1; k < graph.numNode; k++) {//有graph.numNode个顶点的话,生成graph.numNode - 1条边 for (int i = 0; i < graph.numNode; i++) {//i节点:找到访问过的节点 for (int j = 0; j < graph.numNode; j++) {//j节点:找到没有访问过的节点 if (visited[i]==1&&visited[j]==0&&graph.weight[i][j]<maxWeight){ //替换maxWeight maxWeight=graph.weight[i][j]; //记录最后最小权值对应的两个顶点 h1=i; h2=j; } } } //此时h1和h2记录了一条最小的边 System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + maxWeight); //将当前这个节点标记为已经访问过的节点 visited[h2]=1; //maxWeight重置为10000 maxWeight=10000; } } } //创建图 每一个MGraph对象表示一个图 class MGraph{ int numNode;//表示图的节点个数 char[] data;//存放节点数据 int[][] weight;//存放边,也就是邻接矩阵 public MGraph(int numNode) { this.numNode = numNode; data=new char[numNode]; weight=new int[numNode][numNode]; } }
1) 某城市新增7个站点(A, B, C, D, E, F, G) ,现在需要修路把7个站点连通
2) 各个站点的距离用边线表示(权) ,比如 A – B 距离 12公里
3) 问:如何修路保证各个站点都能连通,并且总的修建公路总里程最短?
也是求最小生成树
1) 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法。
2) 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路
3) 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止
代码实现:
import java.util.Arrays; import java.util.Comparator; import static com.gou.kruskal.KruskalCase.INF; public class KruskalCase { public static final int INF=Integer.MAX_VALUE; public static char[] data = {'A','B','C','D','E','F','G'}; public static int[][] weight ={ {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,2,0,9}, {14,INF,INF,INF,8,9,0} }; public static int edgeNum; public static void main(String[] args) { //创建一个图对象 Graph graph=new Graph(data.length); graph.data=data; graph.weight=weight; edgeNum = graph.getEdgeNum(); kruskal(); } //将边的数组按照权值大小进行排序 public static void sortEData(EData[] edges){ Arrays.sort(edges, new Comparator<EData>() { @Override public int compare(EData o1, EData o2) { return o1.weight-o2.weight; } }); } //返回对应顶点的下标 public static int getPosition(char ch){ for (int i = 0; i < data.length; i++) { if (data[i]==ch){ return i; } } //找不到 return -1; } /** * 根据邻接矩阵获取图中的边的集合 * EData[] 形式 [{'A','B',12},['B','F',7]...] * @return */ public static EData[] getEdges(){ EData[] edges=new EData[edgeNum]; int index=0; for (int i = 0; i < data.length; i++) { for (int j = i+1; j < data.length; j++) { if (weight[i][j]!=INF){ edges[index]=new EData(data[i],data[j],weight[i][j]); index++; } } } return edges; } /** * 功能:获取下标为i的顶点的终点(),用于后面判断两个顶点的终点是否相同 * @param end 数组就是记录了各个顶点对应的终点是哪个,ends数组是在遍历过程中,逐步形成 * @param i 表示传入的顶点对应的下标 * @return 返回的就是下标为i的这个顶点对应的终点的下标 */ public static int getEnd(int[] end,int i){ while (end[i]!=0){ i=end[i]; } return i; } public static void kruskal(){ int index=0;//表示最后结果数组的索引 int[] ends=new int[edgeNum];//用于保存"已有最小生成树"中的每个顶点在最小生成树中的终点 //创建结果数组,保存最后的最小生成树 EData[] res=new EData[edgeNum]; //获取图中所有的边的集合 EData[] edges = getEdges(); //按照边的权值大小进行排序(从小到大) sortEData(edges); //遍历edges数组,将边添加到最小生成树中时,判断是准备加入的边否形成了回路,如果没有,就加入rets,否则不能加入 for (int i = 0; i < edgeNum; i++) { //获取到第i条边的第一个顶点(起点) int p1 = getPosition(edges[i].start); //获取到第i条边的第2个顶点 int p2 = getPosition(edges[i].end); //获取p1这个顶点在已有最小生成树中的终点 int m = getEnd(ends, p1); //获取p2这个顶点在已有最小生成树中的终点 int n = getEnd(ends, p2); //是否构成回路 if (m!=n){ ends[m]=n; res[index++]=edges[i]; } } System.out.println("最小生成树为"); for (int i = 0; i < index; i++) { System.out.println(res[i]); } } } class Graph{ int edgeNum;//边的数量 int nodeNum;//顶点数量 char[] data;//顶点的数值 int[][] weight;//邻接矩阵 public Graph(int nodeNum) { this.nodeNum = nodeNum; data=new char[nodeNum]; weight=new int[nodeNum][nodeNum]; edgeNum=0; } //统计边的数量 public int getEdgeNum(){ for (int i = 0; i < data.length; i++) { for (int j = i+1; j < data.length; j++) { if (weight[i][j]!=INF){ edgeNum++; } } } return edgeNum; } //展示邻接矩阵 public void showWeight(){ for (int i = 0; i < weight.length; i++) { for (int j = 0; j < weight[0].length; j++) { System.out.printf("%12d",weight[i][j]); } System.out.println(); } } } 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; } @Override public String toString() { return "EData{" + "start=" + start + ", end=" + end + ", weight=" + weight + '}'; } }