最小生成树
- 连通图:图的连通其实就是树,图的最小连通图其实就是最小生成树。
- 树:如果一个无向连通图中不存在回路,则这种图称为树。
- 生成树:无向连通图G的一个子图如果是一颗包含G的所有顶点的树,则该子图称为G的生成树。
- 最小生成树:或者称为最小代价树,对无向连通图的生成树,各边的权值总和称为生成树的权,权最小的生成树称为最小生成树。
- 一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树。称为最小生成树。
- 找连通网的最小生成树,经典的有两种算法,普里姆算法和克鲁斯卡尔算法。
普里姆算法
假设以v0为基准开始,探测v0到各个顶点的距离:
v0
0, 10,
看到v0到v1的距离最短为10.
接下来我们要把v1加到基准里。以v0和v1为基准,探测到各个顶点的距离:
v0,v1
0, 0, 18,
看到到v8的举例最短为12.
这个过程前会把v0到各个顶点的距离和v1到各个顶点的距离作比较,小的留下。
=======>
v0
0, 10,
v1
10, 0, 18,
可以看到相同位置的元素,18比
v0,v1
0, 0, 18,
=======>
就是这样不断寻找基准距离最短的顶点,将其加入基准。然后再以基准探测周围举例最短的点。一直到所有顶点都找完!
package com.cx.graphdemo;
public class Graph {
private int vertexSize;
private int[] vertexs;
private int[][] matrix;
private static final int MAX_WEIGHT = 1000;
private boolean[] isVisited;
public Graph(int vertexSize) {
this.vertexSize = vertexSize;
vertexs = new int[vertexSize];
matrix = new int[vertexSize][vertexSize];
for (int i = 0; i < vertexSize; i++) {
vertexs[i] = i;
}
isVisited = new boolean[vertexSize];
}
/**
* prim 普里姆算法
*/
public void prim() {
int[] lowcost = new int[vertexSize];
int[] adjvex = new int[vertexSize];
int min, minId, sum = 0;
for (int i = 1; i < vertexSize; i++) {
lowcost[i] = matrix[0][i];
}
for (int i = 1; i < vertexSize; i++) {
min = MAX_WEIGHT;
minId = 0;
for (int j = 1; j < vertexSize; j++) {
if (lowcost[j] < min && lowcost[j] > 0) {
min = lowcost[j];
minId = j;
}
}
/**
* 为什么要找到最小元素和下标?
*
* 因为最小元素代表当前的已知顶点到其它顶点的最短路径,我们要得到这个
* 最短路径通向的顶点。然后周而复始。
*
* 核心思想:
*
* 从某一顶点开始,找到该顶点周围的最短路径及此路径通向的顶点。
* 以这两个顶点为准,找到这两个顶点周围的最短路径及此路径通向的顶点。
* ......最终会通过这个"最短路径算法"走完所有的顶点。
*/
sum += min;
lowcost[minId] = 0;
for (int j = 1; j < vertexSize; j++) {
if (lowcost[j] != 0 && matrix[minId][j] < lowcost[j]) {
lowcost[j] = matrix[minId][j];
adjvex[j] = minId;
}
}
}
System.out.println("最小生成树权值和:" + sum);
}
public static void main(String[] args) {
Graph graph = new Graph(9);
int[] a1 = new int[] { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };
int[] a2 = new int[] { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };
int[] a3 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };
int[] a4 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };
int[] a5 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };
int[] a6 = new int[] { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };
int[] a7 = new int[] { MAX_WEIGHT, 16, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
int[] a8 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };
int[] a9 = new int[] { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };
graph.matrix[0] = a1;
graph.matrix[1] = a2;
graph.matrix[2] = a3;
graph.matrix[3] = a4;
graph.matrix[4] = a5;
graph.matrix[5] = a6;
graph.matrix[6] = a7;
graph.matrix[7] = a8;
graph.matrix[8] = a9;
graph.prim();
}
}
克鲁斯卡尔算法
普里姆算法是按顶点来连通图,而克鲁斯卡尔算法则是按边来构成图。
两个顶点确立一条边,没有问题!所有的边可以从小到大排序,也没有问题!
克鲁斯卡尔算法就是按照这个顺序排好的边来连通图。
规则:
1.找到最小的边。
2.探测边的两个顶点是否会构成回环。
3.如果构成回环则放弃这条边,寻找下一条。
4.如果不构成回环,则记录。再寻找下一条。
整个寻找过程,可以看下图:一点点画的,一定能看懂!
注意:这里面有一个点。探测是否会构成回环!克鲁斯卡尔用一个神奇的数组来完成这个探索!
比如:
v4-v7,权重为7,是最小的边。则记录4号元素为7。也就是说:以起始点为下标,以结束点为值!
[0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
v2-v8,权重为8
[0, 0, 8, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
如果这时候第3条为:
v2-v1,权重为5。这不就冲突了吗?因为2号位置上已经有元素了。如果发生这种情况,就以该位置上值为位置放置新顶点。也就是:
[0, 0, 8, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
为什么这样做?因为这是最简单的测试回环的方法。
2号位置是8,代表v2和v8相连。8号位置是1,代表v8和v1相连。
如果再有边是v1-v2,那么就构成了回环,一个三角回环。所以理解这个数组很重要!
package com.cx.graphtraversal;
public class GraphKruskal {
private Edge[] edges;
private int edgeSize;
public GraphKruskal(int edgeSize) {
this.edgeSize = edgeSize;
edges = new Edge[edgeSize];
}
public void miniSpanTreeKruskal() {
int m, n, sum = 0;
int[] parent = new int[edgeSize];
for (int i = 0; i < edgeSize; i++) {
parent[i] = 0;
}
for (int i = 0; i < edgeSize; i++) {
n = find(parent, edges[i].begin);
m = find(parent, edges[i].end);
if (n != m) {
parent[n] = m;
System.out.println("起始顶点:" + edges[i].begin + "---结束顶点:" + edges[i].end + "~权值:" + edges[i].weight);
sum += edges[i].weight;
} else {
System.out.println("第" + i + "条边回环了");
}
}
System.out.println("sum:" + sum);
}
/**
* 将神奇数组进行查询获取非回环的值
*/
public int find(int[] parent, int f) {
while (parent[f] > 0) {
System.out.println("找到起点" + f);
f = parent[f];
System.out.println("找到终点:" + f);
}
return f;
}
public void createEdgeArray() {
Edge edge0 = new Edge(4, 7, 7);
Edge edge1 = new Edge(2, 8, 8);
Edge edge2 = new Edge(0, 1, 10);
Edge edge3 = new Edge(0, 5, 11);
Edge edge4 = new Edge(1, 8, 12);
Edge edge5 = new Edge(3, 7, 16);
Edge edge6 = new Edge(1, 6, 16);
Edge edge7 = new Edge(5, 6, 17);
Edge edge8 = new Edge(1, 2, 18);
Edge edge9 = new Edge(6, 7, 19);
Edge edge10 = new Edge(3, 4, 20);
Edge edge11 = new Edge(3, 8, 21);
Edge edge12 = new Edge(2, 3, 22);
Edge edge13 = new Edge(3, 6, 24);
Edge edge14 = new Edge(4, 5, 26);
edges[0] = edge0;
edges[1] = edge1;
edges[2] = edge2;
edges[3] = edge3;
edges[4] = edge4;
edges[5] = edge5;
edges[6] = edge6;
edges[7] = edge7;
edges[8] = edge8;
edges[9] = edge9;
edges[10] = edge10;
edges[11] = edge11;
edges[12] = edge12;
edges[13] = edge13;
edges[14] = edge14;
}
class Edge {
private int begin;
private int end;
private int weight;
public Edge(int begin, int end, int weight) {
super();
this.begin = begin;
this.end = end;
this.weight = 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;
}
}
public static void main(String[] args) {
GraphKruskal graphKruskal = new GraphKruskal(15);
graphKruskal.createEdgeArray();
graphKruskal.miniSpanTreeKruskal();
}
}