• 【算法设计与分析基础】24、kruskal算法详解


    首先我们获取这个图

    根据这个图我们可以得到对应的二维矩阵图数据

    根据kruskal算法的思想,首先提取所有的边,然后把所有的边进行排序

    思路就是把这些边按照从小到大的顺序组装,至于如何组装

    这里用到并查算法的思路

    * 1、makeset(x),也就是生成单元素集合,也就是每一个节点
    * 2、find(x) 返回一个包含x的子集,这个集合可以看成一个有根树
    * 3、union(x,y) 构造分别包含x和y的不相交的子集子集Sx和Sy的并集,这里尤为关键:!!!!

    了解到这些思路之后,开始我们的算法

    第一步:获取这个文件的矩阵数据,存放到对象中

    package cn.xf.algorithm.ch09Greedy.vo;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    import org.junit.Test;
    
    public class MGraph {
        private int eleSize;
        private int nums[][];
        private List<KruskalBianVo> kruskalBianVos = new ArrayList<KruskalBianVo>();
        
        public MGraph() {
            // TODO Auto-generated constructor stub
        }
        
        public MGraph(int eleSize, int[][] nums) {
            this.eleSize = eleSize;
            this.nums = nums;
        }
        
        public MGraph(File file) throws Exception {
            if(file.exists()) {
              //读取数据流,获取数据源
                FileInputStream fis;
                BufferedInputStream bis;
                try {
                    fis = new FileInputStream(file);
                    //缓冲
                    bis = new BufferedInputStream(fis);
                    byte buffer[] = new byte[1024];
                    while(bis.read(buffer) != -1) {
                        String allData = new String(buffer);
                        String lines[] = allData.split("
    ");
                        int allLines = lines.length;
                        int allColumns = lines[0].split(" ").length;
                        if(allLines < allColumns) {
                            //如果行比较小
                            eleSize = allLines;
                        } else {
                            //否则以列为准
                            eleSize = allColumns;
                        }
                        nums = new int[eleSize][eleSize];
                        for(int i = 0; i < eleSize; ++i) {
                            //对每一行数据进行入库处理
                            String everyNums[] = lines[i].split(" ");
                            for(int j = 0; j < eleSize; ++j) {
                                nums[i][j] = Integer.parseInt(everyNums[j]);
                            }
                        }
                    }
                    
                    //获取这个矩阵的所有边  kruskalBianVos
                    for(int i = 0; i < eleSize; ++i) {
                        for(int j = i + 1; j < eleSize; ++j) {
                            if(nums[i][j] < 999) {
                                KruskalBianVo kruskalBianVo = new KruskalBianVo();
                                kruskalBianVo.setBeginNode(i);
                                kruskalBianVo.setEndNode(j);
                                kruskalBianVo.setLength(nums[i][j]);
                                kruskalBianVos.add(kruskalBianVo);
                            }
                        }
                    }
                    
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("文件不存在");
            }
        }
    
        public int getEleSize() {
            return eleSize;
        }
        public void setEleSize(int eleSize) {
            this.eleSize = eleSize;
        }
        public int[][] getNums() {
            return nums;
        }
        public void setNums(int[][] nums) {
            this.nums = nums;
        }
        
        
        public List<KruskalBianVo> getKruskalBianVos() {
            return kruskalBianVos;
        }
    
        public void setKruskalBianVos(List<KruskalBianVo> kruskalBianVos) {
            this.kruskalBianVos = kruskalBianVos;
        }
    
        public static void main(String[] args) {
            String path = MGraph.class.getResource("").getPath();
            path = path.substring(0, path.indexOf("/vo"));
            File f = new File(path + "/resource/test.txt");
            try {
                MGraph mg = new MGraph(f);
                System.out.println(mg.getKruskalBianVos().size());
                int rr[][] = mg.getNums();
                System.out.println(rr);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
    }
    

      

    数据对象:

     第二步:建立相应的复制类:

     存放边数据vo

    package cn.xf.algorithm.ch09Greedy.vo;
    
    public class KruskalBianVo {
    	
    	private int beginNode; //开始节点的index
    	private int endNode; 	//结束节点的index
    	private int length;	//边长
        public int getBeginNode() {
            return beginNode;
        }
        public void setBeginNode(int beginNode) {
            this.beginNode = beginNode;
        }
        public int getEndNode() {
            return endNode;
        }
        public void setEndNode(int endNode) {
            this.endNode = endNode;
        }
        public int getLength() {
            return length;
        }
        public void setLength(int length) {
            this.length = length;
        }
    	
    }
    

      

    交换辅助类

    package cn.xf.algorithm.ch09Greedy.util;
    
    import java.util.List;
    
    import cn.xf.algorithm.ch09Greedy.vo.KruskalBianVo;
    
    public class Greedy {
    
    	public static void swapKruskalBianVo(List<KruskalBianVo> kruskalBianVo, int left, int right) {
    		if(kruskalBianVo == null || kruskalBianVo.size() <= 1 || left >= right) {
    			return;
    		}
    		
    		//交换节点
    		KruskalBianVo kruskalBianVoTemp = kruskalBianVo.get(left);
    		kruskalBianVo.set(left, kruskalBianVo.get(right));
    		kruskalBianVo.set(right, kruskalBianVoTemp);
    	}
    }
    

      

     对边进行快排辅助类

    package cn.xf.algorithm.ch09Greedy.util;
    
    import java.util.List;
    
    import cn.xf.algorithm.ch09Greedy.vo.KruskalBianVo;
    
    public class QuikSort {
    	//先找中间点
    	public static int getMiddlePoint(List<KruskalBianVo> kruskalBianVo, int left, int right, boolean isMinToMax) {
    		if(kruskalBianVo == null || kruskalBianVo.size() <= 1 || left >= right) {
    			return left;
    		}
    		//开始快排核心程序,就是对数列两边进行交换
    		//1、首选第一个元素作为第一个参照元素
    		//2、设置左边向右遍历的起点,设定右边向左遍历的起点
    		KruskalBianVo midValue = kruskalBianVo.get(left);
    		int leftIndex = left + 1;
    		int rightIndex = right;
    		int count = 0;
    		//循环遍历,知道left跑到right的右边
    		while(leftIndex < rightIndex) {
    			//确定好区间之后交换位置
    			if(isMinToMax) {
    				//从小到大
    				//遍历左边数据
    				while(kruskalBianVo.get(leftIndex).getLength() <= midValue.getLength() && leftIndex < right) {
    					++leftIndex;
    				}
    				//遍历右边数据
    				while(kruskalBianVo.get(rightIndex).getLength() > midValue.getLength() && rightIndex > left) {
    					--rightIndex;
    				}
    			} else {
    				//如果是从大到小
    				//遍历左边数据
    				while(kruskalBianVo.get(leftIndex).getLength() > midValue.getLength()) {
    					++leftIndex;
    				}
    				//遍历右边数据
    				while(kruskalBianVo.get(rightIndex).getLength() < midValue.getLength()) {
    					--rightIndex;
    				}
    			}
    			//交换位置
    			Greedy.swapKruskalBianVo(kruskalBianVo, leftIndex, rightIndex);
    			++count;
    		}
    		//最后一次交换之后是不必要的交换,因为已经错开位置了,这里做一个调整
    		//交换位置
    		if(count > 0) {
    			//如果进入过循环,那么肯定进行了一次,交换,那么要撤销那一次的无效
    			Greedy.swapKruskalBianVo(kruskalBianVo, leftIndex, rightIndex);
    			//吧最开始的位置和中间的位置进行交换
    			//交换位置
    			Greedy.swapKruskalBianVo(kruskalBianVo, left, rightIndex);
    		}
    		
    		//返回中间位置的索引
    		return rightIndex;
    	}
    	
    	public static void sort(List<KruskalBianVo> kruskalBianVo, Boolean isMinToMax) {
    		if(kruskalBianVo == null || kruskalBianVo.size() <= 0)
    			return;
    		if(isMinToMax == null)
    			isMinToMax = true;
    		sort(kruskalBianVo, 0, kruskalBianVo.size() - 1, isMinToMax);
    	}
    	
    	private static void sort(List<KruskalBianVo> kruskalBianVo, int left, int right, Boolean isMinToMax) {
    		if(left < right) {
    			//如果左索引小于右索引,那么执行递归
    			int mid = getMiddlePoint(kruskalBianVo, left, right, isMinToMax);
    			sort(kruskalBianVo, left, mid - 1, isMinToMax);
    			sort(kruskalBianVo, mid + 1, right, isMinToMax);
    		}
    	}
    }
    

      

     存放树节点的tree结构

    package cn.xf.algorithm.tree;
    
    import java.util.List;
    
    /**
     * 树节点
     * 
     * .
     * 
     * @author xiaof
     * @version Revision 1.0.0
     * @see:
     * @创建日期:2017年8月18日
     * @功能说明:
     *
     */
    public class TreeNode {
        private List<TreeNode> nextNodes;
        private Object value;
        private TreeNode parent = null; //指向父节点
        public TreeNode() {
        }
        public TreeNode(List<TreeNode> nextNodes, Object value) {
            this.nextNodes = nextNodes;
            this.value = value;
        }
        public List<TreeNode> getNextNodes() {
            return nextNodes;
        }
        public void setNextNodes(List<TreeNode> nextNodes) {
            this.nextNodes = nextNodes;
        }
        public Object getValue() {
            return value;
        }
        public void setValue(Object value) {
            this.value = value;
        }
    	public TreeNode getParent() {
    		return parent;
    	}
    	public void setParent(TreeNode parent) {
    		this.parent = parent;
    	}
    }
    

      

    最后实现kruskal算法的核心程序

    各个节点选中过程图展示

    package cn.xf.algorithm.ch09Greedy;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import cn.xf.algorithm.ch09Greedy.util.QuikSort;
    import cn.xf.algorithm.ch09Greedy.vo.KruskalBianVo;
    import cn.xf.algorithm.ch09Greedy.vo.MGraph;
    import cn.xf.algorithm.tree.TreeNode;
    
    /**
     * kruskal 算法, 寻找最小生成树
     * 功能: http://blog.csdn.net/luomingjun12315/article/details/47700237
     *      http://blog.csdn.net/niushuai666/article/details/6689285
     *      http://blog.sina.com.cn/s/blog_a00f56270101a7op.html
     * @author xiaofeng
     * @date 2017年8月21日
     * @fileName KruskalAlgorithm.java
     *
     * 判定回环:判定回环的思路是,如果两个节点联通之后,存在回环,那么两个节点往上遍历这颗树,最终肯定会汇集到根节点,
     * 如果两个节点相连的这根线不存在回环中,那么往上遍历节点,两个节点的根就不会重逢
     * 那么这里有个点
     * 1。这根节点的上级节点存储问题
     * 这里采用数组,也就是V[I]标识I的父节点,这样来存储,当没有改变的时候,也就是没有上级的时候,那么根节点就是本身
     * 
     * 2。如何添加节点的父节点
     * 如果这个节点的被修改过了,存在上级节点,那么就把这个a起点作为起点,b作为后续节点
     * 否则,以另一个作为起点
     * 
     * 对于是否回环的问题,可以看看,不相交子集和并查算法
     * 其中并查算法:快速求并的思路,分三步实现
     * 1、makeset(x),也就是生成单元素集合,也就是每一个节点
     * 2、find(x) 返回一个包含x的子集,这个集合可以看成一个有根树
     * 3、union(x,y) 构造分别包含x和y的不相交的子集子集Sx和Sy的并集,这里尤为关键:!!!!
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     * !  这里快速求并的核心思路是,											                       !!
     * !  吧Sy树的根附加到Sx的根上,也就是新子集的根作为X树的根的一个孩子节点附加进入	       !!
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     */
    public class KruskalAlgorithm {
    
        public void kruska(MGraph mg) {
            if(mg == null)
                return;
            //获取图的所有边信息
            List<KruskalBianVo> allBian = mg.getKruskalBianVos();
            //进行排序处理,这里用一个快排
            QuikSort.sort(allBian, true);
            //排序结束之后按照从小到大的顺序进行操作
            int ecounter = 0;
            //为求是否有回环,使用快速求并的方式构造不同的子树
            //1、 makeset 阶段,建立容器,存放森林
            List<TreeNode> allTree = new ArrayList<TreeNode>();
            for(int i = 0; i < mg.getEleSize(); ++i) {
            	TreeNode treeNode = new TreeNode();
            	treeNode.setValue(i); //第i个节点
            	allTree.add(treeNode);
            }
            //根据节点个数,按照A-Z进行设置名字
            char names[] = new char[mg.getEleSize()];
            for(int i = 0; i < mg.getEleSize(); ++i) {
                names[i] = (char) ('A' + i);
            }
            
            //2、find(x)  寻找x节点加入集合中
            int k = 0;
            while(ecounter < mg.getEleSize() && k < allBian.size()) {
            	//获取节点对象
            	KruskalBianVo kruskalBianVo = allBian.get(k);
            	//判断当前边的两个节点加入之后是否有回路
            	int first = kruskalBianVo.getBeginNode();
            	int second = kruskalBianVo.getEndNode();
            	//求并 3、union(x,y)
            	if(union(allTree, first, second)) {
            		//如果顺利加入
            		System.out.println("[" + names[first] + "]=>[" + names[second] + "] 边长为:" + kruskalBianVo.getLength());
            		++ecounter;
            	}
            	++k; // 计数循环
            }
            
        }
        
        /**
         * 判断能否合并的,就是寻找根部父节点是否一致
         * @param allTree
         * @param first
         * @param second
         * @return
         */
        public Boolean union(List<TreeNode> allTree, int first, int second) {
        	TreeNode root1 = getRoot(allTree.get(first));
        	TreeNode root2 = getRoot(allTree.get(second));
        	if(root1 == root2) {
        		return false;
        	} else {
        		//如果不同根,那么把一边的根加入到一边中
        		root2.setParent(root1);
        	}
        	
        	return true;
        }
        
        private TreeNode getRoot(TreeNode current) {
    		if(current.getParent() == null)
    			return current;
    		else {
    			return getRoot(current.getParent());
    		}
    	}
        
        /**
         * 孩子节点是否包含
         * @param root
         * @param value
         * @return
         */
        public Boolean haveChild(TreeNode root, Object value) {
        	//判断颗树是否有对应的孩子节点
        	Boolean result = false;
        	if(root.getValue().equals(value)) {
        		return true;
        	} else {
        		for(int i = 0; i < root.getNextNodes().size(); ++i) {
        			//判断孩子节点的孩子。。。是否包含
        			if(haveChild(root.getNextNodes().get(i), value)) {
        				result = true;
        				break;
        			}
        		}
        	}
        	return result;
        }
        
        public Boolean findParent(TreeNode root, Object value) {
        	//判断颗树是否有对应的孩子节点
        	Boolean result = false;
        	if(root.getParent() != null) {
        		//存在父节点
        		if(root.getParent().getValue().equals(value)) {
        			result = true;
        		} else {
        			result = findParent(root.getParent(), value);
        		}
        	}
        	
        	return result;
        }
        
    }
    

      

    测试结果:

    package algorithm.ch09Greedy;
    
    import java.io.File;
    
    import org.junit.Test;
    
    import cn.xf.algorithm.ch09Greedy.KruskalAlgorithm;
    import cn.xf.algorithm.ch09Greedy.vo.MGraph;
    
    public class KruskalTest {
    
    	@Test
    	public void test() {
    		String path = KruskalTest.class.getResource("").getPath();
    //		System.out.println(path);
    		File inputFile = new File(path + "/test.txt");
    		MGraph mg = null;
    		try {
    			mg = new MGraph(inputFile);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    		KruskalAlgorithm kruskalAlgorithm = new KruskalAlgorithm();
    		
    		kruskalAlgorithm.kruska(mg);
    	
    	}
    	
    	@Test
    	public void test2() {
    	    System.out.println('A' - 1);
    	}
    }
    

      

    展示:

    包结构:

  • 相关阅读:
    第十七篇 ORM跨表查询和分组查询---二次剖析
    第十六篇随机验证码
    第十五篇 用户认证auth
    crawler_网络爬虫中编码的正确处理与乱码的解决策略
    java_Eclipse自动生成作者、日期注释等功能设置_导入 xml方式
    mysql_windows_安装版添加到服务开机自启动
    java_model_dao_自动生成_generator-mybatis-generator-1.3.2 基于maven插件
    java_eclipse_maven_svn_主题彩色插件_全屏插件
    mysql_MYSQL远程登录权限设置
    crawler_phantomjs_windows_linux下demo
  • 原文地址:https://www.cnblogs.com/cutter-point/p/7442923.html
Copyright © 2020-2023  润新知