並查集演算法
阿新 • • 發佈:2020-11-18
班上有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/