代码改变世界

并查集算法

2020-11-18 15:50  muamaker  阅读(276)  评论(0)    收藏  举报

班上有 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/