zoukankan      html  css  js  c++  java
  • 并查集算法

    班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

    给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

    并查集的重要思想在于,用集合中的一个元素代表集合。

    一个有趣的比喻,把集合比喻成公司,而代表元素则是老总。

    [[1,1,0],
     [1,1,0],
     [0,0,1]]
    

      

    一、定义数组:
    p[i] = parent;
    也就是 第 [i] 个人的领导是谁(假设每个人的直属领导只有一个人),显然当公司只有一个人的时候,自己就是老总,所以初始化的时候 p[i] = i;

    二、查找自己公司的老总
    然后递归查找,就可以找到这个公司的老总;
    function find(i) {
    	if (i == p[i]){
    		return i
    	}else{
    		return find(p[i]);
    	}
    }
    

      

    三、并

    当确认i,j两个人是一个公司的,就可以开始合并了,方式有两种
    p[i] = j; 意思是: j 当上了 i 的领导
    p[j] = i; 意思是: i 当上了 j 的领导

    不管怎么样,反正两个人算是一伙的了

    则合并算法如下:
    function union(i,j){
    	let rootI = find(i); // 寻找 i 公司的领导
    	let rootJ = find(j); // 寻找 j 公司的领导
    	
    	// 本意是将 i,j 合并,如果领导人不一样,就需要融合
    	if(rootI != rootJ){
    		p[i] = rootJ; // 或者 p[j] = rootI; 
    	}
    }
    

      

    四、统计公司的个数

    方法1、只需要确定有多少个老总就行。 老总上面是没有领导的,所以当 p[i] = i 的时候,就是老总

    function countSetNumber(){
    	let count = 0;
    	for(let i = 0; i < p.length; i++){
    		if(p[i] === i){
    			++count;
    		}
    	}
    	return count;
    }
    

     

    方法2、理论初始化的时候,公司个数为 p.length   ,  那么每次  union 合并的时候,把个数减一,最后就可以得到结果了

    let count = p.length; // 最开始大家都是自己的领导
    function union(i,j){
    	let rootI = find(i); // 寻找 i 公司的领导
    	let rootJ = find(j); // 寻找 j 公司的领导
    	
    	// 本意是将 i,j 合并,如果领导人不一样,就需要融合
    	if(rootI != rootJ){
    		p[i] = rootJ; // 或者 p[j] = rootI; 
    		count--;// 合并了,公司数量减少
    	}
    }

    五、完整代码

    /**
     * @param {number[][]} M
     * @return {number}
     */
    var findCircleNum = function(M) {
        let len = M.length;
    	let count = len;
    	let p = new Array(len);
    	
    	for(let i = 0; i < len; i++){
    		p[i] = i;
    	}
    	
    	for(let i = 0; i < len; i++){
    		for(let j = i+1; j < len; j++){
    			// 只需要确定 i,j的关系, i -> j 跟 j -> i 是一样的 ,所以 j 不需要从0开始
    			if(M[i][j] === 1){
    				// 说明他两是一个公司的,就进行合并
    				union(i,j);
    			}
    		}
    	}
    	
    	
    	function find(i) {
    		if (i == p[i]){
    			return i
    		}else{
    			return find(p[i]);
    		}
    	}
    	
    	
    	function union(i,j){
    		let rootI = find(i); // 寻找 i 公司的领导
    		let rootJ = find(j); // 寻找 j 公司的领导
    		
    		// 本意是将 i,j 合并,如果领导人不一样,就需要融合
    		if(rootI != rootJ){
    			p[i] = rootJ; // 或者 p[j] = rootI; 
    			count--;// 合并了,公司数量减少
    		}
    	}
    	
    
    	return count;
    };
    
    
    console.log(findCircleNum(
    [[1,1,0],
     [1,1,0],
     [0,0,1]]
    )
    )
    

    六、优化

    方式1、现在公司是一级管一级,每个人都有自己唯一的直属领导,每次查找公司的老总,都要递归的查找,费时费力。

    开始实行扁平化管理,每个员工的直属领导,都是公司的老总。这样可以减少查找次数。

    方式就是,在查找的时候,顺便把每个员工的直属领导,设置为老总:

    	function find(i) {
    		if (i == p[i]){
    			return i
    		}else{
    			p[i] = find(p[i]);// 这一步是查找老总,并给 i 的只接领导
    			return p[i];
    		}
    	}
    

      

    方式2、每次合并,i ,j  有两种状态, 小公司合并到大公司,显然会减少下次查找的复杂度

       则可以记录每个公司的阶级等级。然后选择合并方式。这里不做叙述。

    七、题目来源

    https://leetcode-cn.com/problems/friend-circles/













  • 相关阅读:
    sizeof()使用错例:sizeof(i++)
    修改linux命令符和解决命令符太长的问题
    【转载】阻止拷贝的三种方式
    git命令几个总结
    scp用法
    RAII
    a linked list
    c++11之函数式编程实例
    [转]基于SAML的单点登录介绍
    [转]OAuth、OAuth2与OpenID区别和联系
  • 原文地址:https://www.cnblogs.com/muamaker/p/14000283.html
Copyright © 2011-2022 走看看