並查集入門及例題分析

1、並查集的原理

並查集(Union-Find)是一種樹型的數據結構,用於處理一些不相交集合的合併及查詢問題。
主要涉及兩種操做:合併和查找。 具體地說,初始狀態下,並查集中的元素是互不相交的,通過一系列操做(Union)後,合併成一個集合。 而在進行了某次合併以後,若是想知道:某兩個元素是否已經處在同一個集合中了?這時候就須要查找(Find)操做。
一張江湖解說圖以下:
數組

簡單來說,這個過程能夠理解爲找祖宗和認祖宗的故事。若是此時你想找到楊左使的管理者,就能夠用並查集結構,若是你想讓宋遠橋歸屬到張無忌門派,就可使用到並查集結構。
那並查集的初始與結構是怎麼樣的呢?數據結構

2、並查集的數據結構及實現方式

一、構建並初始化並查集

初始化並查集結構,用上面圖來講,就是先初始有多少個江湖人士,而後默認他們都是各自一派的管理員。代碼以下:優化

class findUnionCollection{
    private int[] s; // 江湖人數的數組集
    int count; // 門派個數
 
 
// 構建江湖人士的數組集和門派個數
    public findUnionCollection(int length){
        s = new int[length];
        s = initCollection(s);
        count = length;
    }
 // 將每一個江湖人士設置爲各自一派的管理者(根節點默認爲-1)
    private int[] initCollection(int[] target){
        for (int i =0;i<target.length;i++){
            target[i]=-1;
        }
        return target;
    }
}
複製代碼

有了各個門派的一個集合,此時咱們應該也要有個能夠找到每一個門派的管理者一個查找方法。spa

代碼講解:code

public int find(int x){
 
 // 當這個成員的所對應的值小於0,即爲-1,則爲門派的管理員,直接返回
        if(s[x]<0){
            return x;
        }
  // 若是不爲0,則這個成員所對應的值是他的直接上層大佬
  // 此時能夠去遞歸這個大佬的大佬,直到找到最終的管理員
        return find(s[x]);
    }
複製代碼

固然,這裏有個優化點,就是咱們這邊只是關心這個門派的管理員,而不關心這個門派的各個層次的關係。因此咱們在查詢的時候,能夠將這層直接指引到管理員,方便下次直接查找,術語簡稱路徑壓縮。
代碼以下:cdn

public int find(int x){
 
 // 當這個成員的所對應的值小於0,即爲-1,則爲門派的管理員,直接返回
        if(s[x]<0){
            return x;
        }
  // 若是不爲0,則這個成員所對應的值是他的直接上層大佬
  // 此時能夠去遞歸這個大佬的大佬,直到找到最終的管理員
      // 將各個門派的成員都指向最終管理員(實際就是路徑壓縮)
        return s[x] = find(s[x]);
    }
複製代碼

有了找祖宗的方法,固然也少不了認祖宗的方法,換江湖的說話,就是投靠另外一門派blog

代碼解析:遞歸

// 合併兩個江湖人士所屬門派
public void unionCollection(int child1,int child2){
 
// 先判斷兩個江湖人士是否所屬一派,若是是,直接返回,無須合併
        if(find(child1)==find(child2)){
            return;
        }
// 以後咱們這個成員所屬門派的管理員的深度進行對比
// 若是兩個管理者的深度同樣,就隨機選一個管理員做爲最終管理員,將加深其深度(減1)
// 若是child1的深度比child2的深度深,則將child2的管理員指向child1管理員
          if(s[find(child1)]<s[find(child2)]){
            s[find(child2)]=find(child1);
        }else {
            if(s[find(child1)]==s[find(child2)]){
                s[find(child2)]--;
            }
            s[find(child1)]=find(child2);
        }
因爲每一次合併,都會減小一個門派,因此門派的數量也就遞減
        count--;
    }
複製代碼

3、例題實戰

一、題目描述

班上有 N 名學生。其中有些人是朋友,有些則不是。他們的友誼具備是傳遞性。若是已知 A 是 B 的朋友,B 是 C 的朋友,那麼咱們能夠認爲 A 也是 C 的朋友。所謂的朋友圈,是指全部朋友的集合。get

給定一個 N * N 的矩陣 M,表示班級中學生之間的朋友關係。若是Mi = 1,表示已知第 i 個和 j 個學生互爲朋友關係,不然爲不知道。你必須輸出全部學生中的已知的朋友圈總數。it

示例 1:

輸入:

[[1,1,0],

[1,1,0],

[0,0,1]]

輸出: 2

說明:已知學生0和學生1互爲朋友,他們在一個朋友圈。

第2個學生本身在一個朋友圈。因此返回2。

示例 2:

輸入:

[[1,1,0],

[1,1,1],

[0,1,1]]

輸出: 1

說明:已知學生0和學生1互爲朋友,學生1和學生2互爲朋友,因此學生0和學生2也是朋友,因此他們三個在一個朋友圈,返回1。

注意:

N 在[1,200]的範圍內。

對於全部學生,有Mi = 1。

若是有Mi = 1,則有Mj = 1。

二、解題思路

1)先構建並初始一個長度爲N的並查集數組,此時朋友圈的數量初始的長度;

2)遍歷M二維數組,判斷每一個學生之間的關係是否爲1,爲1則調合並操做將二者關聯起來,同時對朋友圈的數量遞減;

3)遍歷完則直接返回朋友圈的數量。

三、代碼實例:

class findUnionCollection{
    private int[] s; // 江湖人數的數組集
    int count; // 門派個數
 
 
// 構建江湖人士的數組集和門派個數
    public findUnionCollection(int length){
        s = new int[length];
        s = initCollection(s);
        count = length;
    }
 // 將每一個江湖人士設置爲各自一派的管理者(根節點默認爲-1)
    private int[] initCollection(int[] target){
        for (int i =0;i<target.length;i++){
            target[i]=-1;
        }
        return target;
    }
    
public int find(int x){
 
 // 當這個成員的所對應的值小於0,即爲-1,則爲門派的管理員,直接返回
        if(s[x]<0){
            return x;
        }
  // 若是不爲0,則這個成員所對應的值是他的直接上層大佬
  // 此時能夠去遞歸這個大佬的大佬,直到找到最終的管理員
      // 將各個門派的成員都指向最終管理員(實際就是路徑壓縮)
        return s[x] = find(s[x]);
    }
}
public void unionCollection(int child1,int child2){
 
// 先判斷兩個江湖人士是否所屬一派,若是是,直接返回,無須合併
        if(find(child1)==find(child2)){
            return;
        }
// 以後咱們這個成員所屬門派的管理員的深度進行對比
// 若是兩個管理者的深度同樣,就隨機選一個管理員做爲最終管理員,將加深其深度(減1)
// 若是child1的深度比child2的深度深,則將child2的管理員指向child1管理員
          if(s[find(child1)]<s[find(child2)]){
            s[find(child2)]=find(child1);
        }else {
            if(s[find(child1)]==s[find(child2)]){
                s[find(child2)]--;
            }
            s[find(child1)]=find(child2);
        }
因爲每一次合併,都會減小一個門派,因此門派的數量也就遞減
        count--;
    }
    
class Solution {
    public Solution(){
    }
    public static int findCircleNum(int[][] M) {
        int studentNums = M[0].length;
        findUnionCollection FUC = new findUnionCollection(studentNums);
        for(int i=0;i<studentNums-1;i++){
            for(int j = i+1; j<M[i].length;j++){
                if(M[i][j]==1){
                    FUC.unionCollection(i,j);
                }
            }
        }
        return FUC.getCount();
    }
    public static void main(String[] var0) {
        int[][] M = new int[][]{{1,1,0},{1,1,0},{0,0,1}};
        System.out.println("findUnionCollection:"+findCircleNum(M));
    }
}
複製代碼
相關文章
相關標籤/搜索