• 图的最小生成树的理解和实现: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);
    	}
    }
    
  • 相关阅读:
    判断变量是否是数组
    手机闹钟功能: 使用状态图解决闹钟响铃问题
    logging基本使用方法
    ssh 通过跳板机连接到远程服务器
    ssh 执行命令并实时显示结果
    使用 python 将 " " 转换为 " "
    python 文件操作
    python SMTP 发送邮件
    C++统一初始化
    设计模式之单例模式实现(C++)
  • 原文地址:https://www.cnblogs.com/lishanlei/p/10707822.html
Copyright © 2020-2023  润新知