最小生成树
一个有 n 个结点的带权无向图,在满足所有顶点都连接的前提下使得所有的边的权总和最小,即为最小生成树(Minimum Spanning Tree MST)。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
- N个顶点,一定有N-1条边
- 包含所有顶点
- 所有顶点都可以直接或间接连接到另外的顶点
普里姆算法
普里姆算法在找最小生成树时,将顶点分为两类,一类是在查找的过程中已经包含在树中的(假设为 A 类),剩下的是另一类(假设为 B 类)。
对于给定的连通网,起始状态全部顶点都归为 B 类。在找最小生成树时,选定任意一个顶点作为起始点,并将之从 B 类移至 A 类;然后找出 B 类中到 A 类中的顶点之间权值最小的顶点,将之从 B 类移至 A 类,如此重复,直到 B 类中没有顶点为止。所走过的顶点和边就是该连通图的最小生成树。
- 创建一个VisitedList保存所有已经被访问过的顶点
- 从任意顶点A开始构建最小生成树,标记顶点V已经被访问,加入被访问过顶点集合VisitedList
- 遍历被访问顶点集合 找到在不构成回路的前提下权值最小的边,将新增的顶点加入VisitedList
- 循环直到为顶点-1条边构建完成
图解
1.从顶点开始处理 ======> <A,G> 2
A-C [7] A-G[2] A-B[5] =>
-
<A,G> 开始 , 将A 和 G 顶点和他们相邻的还没有访问的顶点进行处理 =》<A,G,B>
A-C[7] A-B[5] G-B[3] G-E[4] G-F[6] -
<A,G,B> 开始,将A,G,B 顶点 和他们相邻的还没有访问的顶点进行处理=><A,G,B,E>
A-C[7] G-E[4] G-F[6] B-D[9]
…
4.{A,G,B,E}->F//第4次大循环 , 对应 边<E,F> 权值:5
5.{A,G,B,E,F}->D//第5次大循环 , 对应 边<F,D> 权值:4 -
{A,G,B,E,F,D}->C//第6次大循环 , 对应 边<A,C> 权值:7 ===> <A,G,B,E,F,D,C>
代码示例
public class PrimAlgorithmDemo {
public static void main(String[] args) {
//测试看看图是否创建ok
char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int vertex = data.length;
//邻接矩阵的关系使用二维数组表示,10000这个大数,表示两个点不联通
int[][] weight = new int[][]{
{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 graph = new MGraph(vertex);
MiniTree miniTree = new MiniTree();
miniTree.createGraph(graph, vertex, data, weight);
miniTree.prim(graph, 0);
}
}
class MiniTree {
//创建图的邻接矩阵
/**
* @param graph 图对象
* @param vertex 图对应的顶点个数
* @param data 图的各个顶点的值
* @param weight 图的邻接矩阵
*/
public void createGraph(MGraph graph, int vertex, char data[], int[][] weight) {
int i, j;
for (i = 0; i < vertex; i++) {//顶点
graph.data[i] = data[i];
for (j = 0; j < vertex; j++) {
graph.weight[i][j] = weight[i][j];
}
}
}
//显示图的邻接矩阵
public void showGraph(MGraph graph) {
for (int[] link : graph.weight) {
System.out.println(Arrays.toString(link));
}
}
/**
* 使用普里姆算法生成最小生成树
*
* @param graph 图
* @param start 开始顶点
*/
public void prim(MGraph graph, int start) {
// 创建一个set用于保存 所有已经被访问过的顶点
Set<Integer> visited = new HashSet<>();
visited.add(start);
// 一直所有顶点都被访问才结束
int v1 = 10000; // 变量记录当前顶点
int v2 = 10000; // 记录下一个连接的顶点
while (visited.size() < graph.vertex) {
int minWeight = 10000;
// 遍历循环已经 访问过的顶点
for (Integer v : visited) {
// 获取已经访问顶点中所有 非回路的边中 权值最小的一个
for (int i = 0; i < graph.vertex; i++) {
// 如果当前顶点还没有被访问过 并且 找到权值最小的一个
// 这里v是已经联通了点 i是还未联通的点 graph.weight[v][i] < minWeight本质上是在找一条权值最小的一条边
if (!visited.contains(i) && graph.weight[v][i] < minWeight) {
// 记录当前最小权值边的顶点和权值
v1 = v;
v2 = i;
minWeight = graph.weight[v][i];
}
}
}
// 上面双层for循环完之后就已经找到了 v2下一个如要加入的顶点 和最小权限 minWeight
// 将v2标记为已访问
visited.add(v2);
// 打印信息
System.out.println(graph.data[v1] + " => " + graph.data[v2] + " weight=" + minWeight);
}
}
}
class MGraph {
int vertex; //表示图的节点个数
char[] data;//存放结点数据
int[][] weight; //存放边,就是我们的邻接矩阵
public MGraph(int vertex) {
this.vertex = vertex;
data = new char[vertex];
weight = new int[vertex][vertex];
}
}