最小生成树表示得是连通图的极小连通子图,它包含全部的的顶点,但足以生成n-1条边的数。算法
下面是我学习的内容和理解。数组
1.使用普里姆算法构成最小生成树。数据结构
先讲一下普里姆算法的思路。普里姆算法思路是这样的:学习
前提:G={V,E} 这个是咱们图的定义这个应该是明白啥意思的。ui
1.如今定义一个V1表示一个空的新集合this
2.首先随机的将V中的一个节点放入到V1中,做为遍历图开始位置spa
3.其次选取V1中的一个节点和选取V中的一个节点,使边的权值是E中最小的(必须是存在E中的,否则不是两个节点不是连通的,也没用意义。第一次从V1只能选取咱们随机取的那个节点啦,可是后面须要看状况)code
4.再将V中的那个节点放入到V1中来,而且将咱们刚才在第三3步使用过的那边条从E中删除,重复第3步,只到V1=V.blog
下面是JAVA实现:get
private int edgeSize; private int vertexeSize; public static final int MAX_VALUE = Integer.MAX_VALUE; private List<Edge> minimumTreeEdges = new ArrayList<>(); //最小生成树 n-1 private int[][] arc = new int[9][9]; //边集合 邻接矩阵表示方式 /** * 普里姆构建最小生成树 */ public void buildMinimumTreeByPrim() { int[] minimumLine = new int[vertexeSize]; int[] minimumLine2LastSearched = new int[vertexeSize]; //这里是记录上一次最小权值节点的下标 for (int i = 1; i < vertexeSize; i++) { minimumLine[i] = arc[0][i]; //首先将第一排的数据做为最小的一列值后面将从这其中查找最小权值 minimumLine2LastSearched[i] = 0; } minimumLine[0] = 0; //0已经在加入到了遍历过的节点中去 minimumLine2LastSearched[0] = 0;//表示0是0的上一次的最小权值(由于0是开始,因此前面仍是0了) for (int i = 1; i < vertexeSize; i++) { int k = 0, min = MAX_VALUE; for (int j = 1; j < vertexeSize; j++) { if (minimumLine[j] != 0 && minimumLine[j] < min) { //这里是找目前全部的边中权值最小的那个 min = minimumLine[j]; k = j; } } // NotBoundEdge edge = new NotBoundEdge(minimumLine2LastSearched[k], k); //若是是第一次的话 为0 由于由0这个最小权值计算出了k这个最小权值 // minimumTreeEdges.add(edge); System.out.println("("+minimumLine2LastSearched[k]+","+k+")"); minimumLine[k] = 0; //由于这个k已经遍历过了 因此设置为0 for (int j = 0; j < vertexeSize; j++) { if (minimumLine[j] != 0 && arc[k][j] < minimumLine[j]) { minimumLine[j] = arc[k][j]; //若是有比其余更小权值得节点 那么就覆盖(更大的权值已经没有任何意义了) minimumLine2LastSearched[j] = k; //这里就表示k是j的上一个节点(若是权值最小 也就是: (k,j)) } } } }
上面不是我本身实现的,上面的思路是网上最为流行的,因此就将这种算法理解了一下(把变量名改了一下,之前的i,j,k真是不能忍),而后加了一些本身的理解.我以为这个算法很很差理解的地方在于
int[] minimumLine = new int[vertexeSize]; int[] minimumLine2LastSearched = new int[vertexeSize]; //这里是记录上一次最小权值节点的下标
这两个数组,由于的代码基本都没怎么讲明白(可能恰好我看的几遍都没讲清楚),因此我就加上本身的注释了,linimumLine 一开始记录的和0相关的权值,可是到了后来就不是了,是记录了每一行只要比它对应的那一列小的权值.
2 克鲁斯卡尔算法
这个算法的原理是这样的:
前提:G={V,E}
1.先定义 一个T={V1,{}},初始化的时候,T中是没有边的,只有n个顶点,图中的每个节点都做为一个连通份量
2.在E中选择代价最小的边,若边依附于不用的连通份量,那么就将这个边放到T中,不然就丢弃这条边,一直到T中的全部的几点都在一个连通份量上
代码:
private NetNode[] netNodes=new NetNode[15];
/** * TODO * 根据克鲁斯卡尔构建最小生成树 */ public void buildMinimumByKruskal() { int[] parent=new int[edgeSize]; for (int i=0;i<vertexeSize;i++){ parent[i]=0; } int m,n; for (int i=0;i<vertexeSize;i++){ /** * 若是这里求出的n=m的话 那么就表示构成环了( * 由于parent[index]>0的话 就会接着往下走,直到parent[index]=0. * 这条路线能够当作是边连在一块儿的. * 这里就至关于从begin 和end 这两个点开始往下一直连线[直到parent[index]=0结束]. * 若是碰到了一块(m=n) 那么就说明他们相遇了 * ) */ m=findNode(parent,netNodes[i].getBegin()); n=findNode(parent,netNodes[i].getEnd()); if(m!=n){ parent[n]=m; System.out.println("("+netNodes[i].begin+","+netNodes[i].getEnd()+")"); } } } /** * 这里比较难理解 * 1.这里其实就是想查看是否在用一个子集(若是parent[index]=0 * 那就也就表示没有连在一块儿) * @param parent * @param index * @return */ public int findNode(int[] parent, int index) { while (parent[index] > 0) { index = parent[index]; } return index; } public interface Edge { int getBegin(); int getEnd(); int getWeight(); } /** * */ public class NetNode implements Edge { private int begin; private int end; private int weight; public int getBegin() { return begin; } public void setBegin(int begin) { this.begin = begin; } public int getEnd() { return end; } public void setEnd(int end) { this.end = end; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } }
注意我这里没体现出的一个地方是:放进去的netNodes是按照权值从小到大的:
参考:
大话数据结构 第七章