1. 程式人生 > 實用技巧 >並查集演算法

並查集演算法

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