最小生成树
一个连通图的生成树是一个极小的连通子图,它含有图中所有的顶点,但只有足以构成一棵树的n-1条边。我们将构造连通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)。
普利姆算法(Prim)
定义
假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:
在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。
此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。
普利姆算法在运行中始终保持TE中的边集构成一棵生成树。
一个例子(该示例参见:https://www.cnblogs.com/PJQOOO/p/3855017.html)
1. 在进行Prim算法时,先选择一个顶点作为起始点,一般情况下选则v1,设U集合为当前所找到最小生成树里面的顶点,TE集合为所找到的边。此时状态为:
U={v1}; TE={};
2. 现在查找一个顶点在U集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值
通过图中我们可以看到边v1-v3的权值最小为1,那么将v3加入到U集合,(v1,v3)加入到TE,状态如下:
U={v1,v3}; TE={(v1,v3)};
3. 继续寻找,现在状态为U={v1,v3}; TE={(v1,v3)};在与红线相交的边上查找最小值。我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:
我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:
U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}
如此循环一下直到找到所有顶点为止。
4. 下图像我们展示了全部的查找过程:
代码实现(Java):
package 最小生成树;
public class Prim {
private final int INFINITY = 65535;
private int MAXVEX;
public int[][] initArray() {
//模拟无向图的邻接矩阵
int maze[][] = {
{ 0, 10,INFINITY, INFINITY, INFINITY, 11, INFINITY, INFINITY, INFINITY },
{ 10, 0, 18, INFINITY, INFINITY, INFINITY, 16, INFINITY, 12 },
{ INFINITY, INFINITY, 0, 22,INFINITY,INFINITY,INFINITY,INFINITY, 8 },
{ INFINITY, INFINITY, 22, 0, 20, INFINITY, INFINITY, 16, 21 },
{ INFINITY, INFINITY, INFINITY, 20, 0, 26, INFINITY, 7, INFINITY},
{11, INFINITY, INFINITY, INFINITY, 26,0, 17, INFINITY, INFINITY},
{INFINITY, 16, INFINITY,INFINITY,INFINITY, 17, 0, 19, INFINITY},
{INFINITY, INFINITY,INFINITY, 16, 7, INFINITY, 19, 0, INFINITY},
{INFINITY, 12, 8,21,INFINITY,INFINITY,INFINITY,INFINITY,0}
};
this.MAXVEX = maze.length;
return maze;
}
public void MiniSpanTreePrim(int[][] G) {
int min, j, k;
int[] adjvex = new int[this.MAXVEX];//保存相关顶点下标
int[] lowcost = new int[this.MAXVEX];//保存相关顶点间边的权值
lowcost[0] = 0; //初始化第一个权值为0 ,即V0加入生成树
adjvex[0] = 0; //初始化第一个顶点下标为0
for (int i = 1; i < this.MAXVEX; i++) {
lowcost[i] = G[0][i];
adjvex[i] = 0;
}
for (int i = 1; i < this.MAXVEX; i++) {
min = INFINITY;
j = 1; k = 0;
while(j < G.length ) {
if(lowcost[j] != 0 && lowcost[j] < min) {
min = lowcost[j];
k = j;
}
j++;
}
System.out.println("(" + adjvex[k] + "," + k + ")");
lowcost[k] = 0;
for (j = 1; j < this.MAXVEX; j++) {
if(lowcost[j] != 0 && G[k][j] < lowcost[j]) {
lowcost[j] = G[k][j];
adjvex[j] = k;
}
}
}
}
public static void main(String[] args) {
Prim prim = new Prim();
int[][] g = prim.initArray();
prim.MiniSpanTreePrim(g);
}
}
克鲁斯卡尔算法(Kruskal)
在了解Kruskal算法前,我们需要了解的数据结构有:
边集数组
边集数组是由两个一维数组构成。一个是存储顶点的信息,一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标、权组成。
并查集
并查集主要用在判断一个图中的两个顶点是否能相联通的问题。可以参见这篇博客:https://blog.csdn.net/w1085541827/article/details/52076481
这里不做赘述。
右边的就是该图的边集数组,以边为单位,存储着这条边的首尾两个顶点,还有这条边的权值,这里注意的是对于顶点的前后顺序是没有关系的,比如第一行的(0, 1),也可以存成(1, 0)。边集数组以边为基项,有几条边就有几项。
在边集数组右边的就是并查集,并查集是一个用双亲表示法所表示的森林,利用森林来查找某个顶点的根节点是哪个,如果两个顶点的根节点相同,证明两个顶点在同一颗树中,那么以这两个顶点构成的边就不能出现在最小生成树中。通过并查集,我们来确定在生成树中加上一条边后是否会形成环。并查集以顶点为基项,有几个顶点就有几项。
步骤:
1. 对图的存储边集数组进行排序,按权重值由小到大进行排序。
2. 初始化并查集,每一个位置中的值初始化为其对应下标(也可以初始化每一个位置为零)
3. 选取边集数组中最小项(第一项),查询该边所对应的顶点在并查集中是否同源。
若同源:则跳过,继续向下遍历存储结构
若不同源:将该边加入生成树中,将该边的结尾顶点放入下标为起点并查集中,即修改后者的根在并查集中位置的值为前者的 根。
代码实现(Java):
package 最小生成树;
public class Kruskal {
private final int INFINITY = 65535;
private int numEdges = 15;
class Edges {
int begin;
int end;
int weight;
public Edges (int begin, int end, int weight) {
this.begin = begin;
this.end = end;
this.weight = weight;
}
}
public void MiniSpanTreeKruskal(int[][] G) {
int n, m, e = 0;
Edges[] edges = new Edges[numEdges];
int[] parent = new int[G.length];
for (int i = 0; i < G.length; i++) {
for (int j = 0; j < i; j++) {
if(G[i][j] != 0 && G[i][j] < INFINITY) {
Edges ed = new Edges(i, j, G[i][j]);
edges[e] = ed;
e++;
}
}
}
for (int i = 0; i < G.length; i++) {
parent[i] = 0;
}
for (int i = 0; i < numEdges; i++) {
n = Find(parent, edges[i].begin);
m = Find(parent, edges[i].end);
if(m != n) {
parent[n] = m;
System.out.println("(" + edges[i].begin + "," + edges[i].end + "," + edges[i].weight + ")");
}
}
}
private int Find(int[] parent, int f) {
while(parent[f] > 0) {
f = parent[f];
}
return f;
}
public static void main(String[] args) {
Prim prim = new Prim();
int[][] g = prim.initArray();
Kruskal k = new Kruskal();
k.MiniSpanTreeKruskal(g);
}
}