<pre code_snippet_id="1933514" snippet_file_name="blog_20161017_1_459168" name="code" class="java">一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。所谓的最小成本,就是n个顶点,
用n-1条边把一个连通图连接起来,并且使得权值的和最小。综合以上两个概念,我们可以得出:构造连通网的最小代价生成树,即最小生成树(Minimum Cost Spanning Tree)。
找连通图的最小生成树,经典的有两种算法,普里姆算法和克鲁斯卡尔算法首先咱们来说普里姆算法
<span style="font-family: Arial, Helvetica, sans-serif;">先构造一个邻接矩阵 <img src="https://github.com/wangkuiwu/datastructs_and_algorithm/blob/master/pictures/graph/prim/01.jpg?raw=true" alt="" /></span><span style="font-family: Arial, Helvetica, sans-serif;"> </span><span style="font-family: Arial, Helvetica, sans-serif;"> </span>
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family:Arial, Helvetica, sans-serif;">以上图为例 从A点开始用普里姆算法构造一个最小生成树</span>
<span style="font-family:Arial, Helvetica, sans-serif;">
</span>
<span style="font-family:Arial, Helvetica, sans-serif;"><img src="https://github.com/wangkuiwu/datastructs_and_algorithm/blob/master/pictures/graph/prim/02.jpg?raw=true" alt="" />
</span>
<span style="font-family:Arial, Helvetica, sans-serif;">
</span>
<span style="font-family:Arial, Helvetica, sans-serif;"></span><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-size: 16px; color: rgb(51, 51, 51); word-wrap: break-word; word-break: normal; font-family: "Microsoft YaHei", Arial, 宋体;"><span style="color: rgb(0, 0, 0);"><strong>初始状态</strong>:V是所有顶点的集合,即V={A,B,C,D,E,F,G};U和T都是空!
<strong>第1步</strong>:将顶点A加入到U中。
此时,U={A}。
<strong>第2步</strong>:将顶点B加入到U中。
上一步操作之后,U={A}, V-U={B,C,D,E,F,G};因此,边(A,B)的权值最小。将顶点B添加到U中;此时,U={A,B}。
<strong>第3步</strong>:将顶点F加入到U中。
上一步操作之后,U={A,B}, V-U={C,D,E,F,G};因此,边(B,F)的权值最小。将顶点F添加到U中;此时,U={A,B,F}。
<strong>第4步</strong>:将顶点E加入到U中。
上一步操作之后,U={A,B,F}, V-U={C,D,E,G};因此,边(F,E)的权值最小。将顶点E添加到U中;此时,U={A,B,F,E}。
<strong>第5步</strong>:将顶点D加入到U中。
上一步操作之后,U={A,B,F,E}, V-U={C,D,G};因此,边(E,D)的权值最小。将顶点D添加到U中;此时,U={A,B,F,E,D}。
<strong>第6步</strong>:将顶点C加入到U中。
上一步操作之后,U={A,B,F,E,D}, V-U={C,G};因此,边(D,C)的权值最小。将顶点C添加到U中;此时,U={A,B,F,E,D,C}。
<strong>第7步</strong>:将顶点G加入到U中。
上一步操作之后,U={A,B,F,E,D,C}, V-U={G};因此,边(F,G)的权值最小。将顶点G添加到U中;此时,U=V。 </span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-size: 16px; color: rgb(51, 51, 51); word-wrap: break-word; word-break: normal; font-family: "Microsoft YaHei", Arial, 宋体;"><span style="color: rgb(0, 0, 0);">此时,最小生成树构造完成!它包括的顶点依次是:<strong>A B F E D C G</strong>。</span></p>
代码说明:
class <span style="font-family: "Microsoft YaHei", Arial, 宋体; font-size: 16px;">MatrixUDG{</span>
private char[] mVexs; // 顶点集合
private int[][] mMatrix; // 邻接矩阵
private static final int INF = Integer.MAX_VALUE; // 最大值
...
}
/*
* prim最小生成树
*
* 参数说明:
* start -- 从图中的第start个元素开始,生成最小树
*/
public void prim(int start) {
int num = mVexs.length; // 顶点个数
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值
// prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
prims[index++] = mVexs[start];
// 初始化"顶点的权值数组",
// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (int i = 0; i < num; i++ )
weights[i] = mMatrix[start][i];
// 将第start个顶点的权值初始化为0。
// 可以理解为"第start个顶点到它自身的距离为0"。
weights[start] = 0;
for (int i = 0; i < num; i++) {
// 由于从start开始的,因此不需要再对第start个顶点进行处理。
if(start == i)
continue;
int j = 0;
int k = 0;
int min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}
// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k];
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && mMatrix[k][j] < weights[j])
weights[j] = mMatrix[k][j];
}
}
// 计算最小生成树的权值
int sum = 0;
for (int i = 1; i < index; i++) {
int min = INF;
// 获取prims[i]在mMatrix中的位置
int n = getPosition(prims[i]);
// 在vexs[0...i]中,找出到j的权值最小的顶点。
for (int j = 0; j < i; j++) {
int m = getPosition(prims[j]);
if (mMatrix[m][n]<min)
min = mMatrix[m][n];
}
sum += min;
}
// 打印最小生成树
System.out.printf("PRIM(%c)=%d: ", mVexs[start], sum);
for (int i = 0; i < index; i++)
System.out.printf("%c ", prims[i]);
System.out.printf("n");
}
而另一个算法则是采用的另一种思想
克里斯卡尔算法
先构造一个连通图
克里斯卡尔算法重要是用连通图的边形成回环来判断
流程大致如下:
1.将无向图的边按距离长短递增式排序,放到集合中
2.遍历该集合,找出最短的边,加入到结果生成树的集合中
3.如果结果生成树出现回路,则放弃这条边
4.重新执行步骤2,直至所有顶点被遍历
可以看出在每次遍历过程中采用了贪心算法
实体
- /*
- * 克鲁斯卡尔->最小生成树边实体
- * */
- public class Edge {
- private int begin;
- private int end;
- private int weight;