图的最小生成树的理解和实现:Prim和Kruskal算法

最小生成树

一个连通图的生成树是一个极小的连通子图,它含有图中所有的顶点,但只有足以构成一棵树的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);
	}
}
原文地址:https://www.cnblogs.com/lishanlei/p/10707822.html