並查集(二)並查集的算法應用案例上

直接看本文的,建議先看並查集(一)並查集的幾種實現。並查集的題在力扣上都是中等題或者難度題,這個特殊的數據結構還有一些門檻html

P261. 以圖判樹

力扣第261題 這道題應該算是最適合去理解並查集的 https://leetcode-cn.com/problems/graph-valid-tree/數組

題目

給定從 0 到 n-1 標號的 n 個結點,和一個無向邊列表(每條邊以結點對來表示),
請編寫一個函數用來判斷這些邊是否可以造成一個合法有效的樹結構。

示例 1:

輸入: n = 5, 邊列表 edges = [[0,1], [0,2], [0,3], [1,4]]
輸出: true

示例 2:

輸入: n = 5, 邊列表 edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
輸出: false

題意

咱們首先要理解 一個合法有效的樹結構的意思,關鍵在於什麼樣的結構是一個合法有效樹結構,這裏給的邊的列表是無向的,無向也很重要,這樣你不用考慮節點遍歷須要有方向性。數據結構

  • 全部節點組成一棵樹,全部節點都會鏈接到一塊兒,鏈接到一個頂點上
  • 是樹結構,不能有環,來研究一下環的狀況
o a              o a
 / \   樹         / \   圖 
o   o b        c o — o b

前面是樹,咱們來考慮連接的點,鏈接點a、b,連通前它們各自的點處於的狀態是 不連通的狀態
再來看一下後面圖中的b、c的狀況,b和c在沒有直接鏈接時,它們是否是已經處於連通的狀態了,由於它們已經經過頂點a連通了函數

代碼思路:經過並查集合並全部節點,若是成樹知足兩個條件性能

  • 最後只剩下一株
  • 合併兩個點以前,連個點處於不連通的狀態

這裏咱們拿以前最後一種路徑壓縮的,以前講過的五種方法中,任意選一種均可以,只是一般都選擇後面兩種性能較好的。同時注意一下,這裏咱們還在並查集的類中加入了一個成員變量plant,把並查集看作多棵樹的話,最開始全部節點沒有合併,樹的總數爲元素個數size,合併一次,樹就減小一棵,因此join方法中plant--;code

並查集類

public class UFRankUnionCompressPath {


    int[] parent;
    int[] rank;//樹的層數
    int plant;  //一共有多少株樹

    public UFRankUnionCompressPath(int size) {
        plant = size;
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < parent.length; i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    private int findP(int x) {//查詢操做,實際上是查詢  根節點
        if (x < 0 || x >= parent.length)
            return -1;  //或者直接拋出異常

        while (parent[x] != parent[parent[x]])
        //若是parent[x]==parent[parent[x]] 說明這顆樹到了第二層,或者第一層
        //第一層 由於parent[x]=x因此有  parent[x]==parent[parent[x]]
        //第二層 由於第二層的parent是根節點 有: parent[x]= root  
        //因此有 parent[parent[x]]= parent[root]  而自己 parent[root]=root
        {
            // x = parent[x];  //
            parent[x] = parent[parent[x]]; //將下層節點往頂層提高,最終
        }
        return parent[x];
    }


    public void join(int a, int b) {
        int ap = findP(a);
        int bp = findP(b);
        if (ap != bp) {
            plant--;
            //a的層數越高,就將層數少的合併到層數高的上面
            if (rank[ap] > rank[bp])
                parent[bp] = ap;
            else if (rank[ap] < rank[bp]) {
                parent[ap] = bp;
            } else {
                //相同狀況的話,隨便就能夠
                parent[ap] = bp;
                rank[bp]++;
            }
        }
    }

    public int getPlant() {
        return plant;
    }

    public boolean isJoined(int a, int b) { //兩個節點是不是  鏈接的
        return findP(a) == findP(b);
    }
}

具體解題實現

class Solution {
    public boolean validTree(int n, int[][] edges) {
            UFRankUnionCompressPath uf = new UFRankUnionCompressPath (n);
            for (int[] edge : edges) {
                //連通前是斷開狀態
                if(uf.isJoined(edge[0],edge[1])){
                     return false;
                }
                uf.join(edge[0], edge[1]);
            }
            //最後連通只有一棵樹
            return uf.getPlant()==1;    
    
        }
}

另一種實現

除了上面這種實現,這裏還有另一種實現方式。若是是一棵樹,邊數和頂點數是知足如下關係的
邊數 = 點數 - 1,具體的定理的證實你能夠試試。在這種狀況下,咱們就不用去判斷鏈接前是不連通的狀態了,若是有邊鏈接前是連通的,那它必定也不知足邊數 = 點數 - 1htm

class Solution {
    public boolean validTree(int n, int[][] edges) {
        //樹,首先要知足邊數 = 點數 - 1,其次是保證聯通。
        if (n < 1 || edges.length != n - 1) {
            return false;
        }

        UFRankUnionCompressPath uf = new UFRankUnionCompressPath (n);
        for (int[] edge : edges) {
            uf.join(edge[0], edge[1]);
        }
        
        return uf.getPlant()==1;

    }
}

695. 島嶼的最大面積

力扣第695題 這題比剛剛那道題稍微複雜了一點點,但理解了仍是很簡單的
https://leetcode-cn.com/problems/max-area-of-island/blog

題目

給定一個包含了一些 0 和 1 的非空二維數組 grid 。

一個 【島嶼】是由一些相鄰的 1 (表明土地) 構成的組合,這裏的「相鄰」要求兩個 1 
必須在水平或者豎直方向上相鄰。你能夠假設 grid 的四個邊緣都被 0(表明水)包圍着。

找到給定的二維數組中最大的島嶼面積。(若是沒有島嶼,則返回面積爲 0 。)


示例 1:

輸入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]

輸出:4

題意

題意是比較簡單的,就是說挨着的1就能鏈接成陸地(但只在水平和錘子方向挨着,斜着不算),計算最大的陸地鏈接了幾個點,如題中給出的案例,最大島嶼是數組左上角鏈接的4個數字。leetcode

解題思路

咱們知道並查集是解決連通性的問題,那麼能夠這樣用並查集鏈接上陸地的點,再經過計數算出該連通的樹的節點數。咱們又須要再次將以前的並查集作必定的修改,增長一個記錄節點數的變量,由於是每一個節點都有,使用數組記錄。get

class Solution {
     public int maxAreaOfIsland(int[][] grid) {

        int h = grid.length;
        int w = grid[0].length;
        UFRankUnionPlant uf = new UFRankUnionPlant(w * h);
        boolean noJoin = true;
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                //int index = w * i + j;
                if (grid[i][j] == 1) {
                    noJoin = false;
                    //作合併操做
                    if (i - 1 >= 0 && grid[i - 1][j] == 1) {
                        //將二維數組點映射成一維的點
                        uf.join(w * (i - 1) + j, w * i + j);  
                    }

                    if (j - 1 >= 0 && grid[i][j - 1] == 1) {
                        uf.join(w * i + j - 1, w * i + j);
                    }
                }
            }
        }
        //特別注意,由於並查集的初始的len必定是1,但若是沒產生任何合併,最大島嶼是0
        return  noJoin? 0:  uf.maxLen;  
    }
}

//基於並查集修改的類
public class UFRankUnionPlant {

    int[] parent;
    int[] rank;//樹的層數
    int plant;//株
    int maxLen; //全局記錄最大株的成員變量
    int[] len;  //記錄每株數的節點數的數組

    public UFRankUnionPlant(int size) {
        parent = new int[size];
        rank = new int[size];
        len = new int[size];    //記錄根的節點數
        plant = size;
        maxLen = 1;
        for (int i = 0; i < parent.length; i++) {
            parent[i] = i;
            rank[i] = 1;
            len[i] = 1;
        }
    }


    public int findP(int x) {//查詢操做,實際上是查詢  根節點
        if (x < 0 || x >= parent.length)
            return -1;  //或者直接拋出異常

        while (parent[x] != x)//一直搜索到根節點
            x = parent[x];

        return x;
    }


    public void join(int a, int b) {
        int ap = findP(a);
        int bp = findP(b);
        if (ap != bp) {
            plant--;
            //a的層數越高,就將層數少的合併到層數高的上面
            int joinlen = len[ap] + len[bp];
            maxLen = Math.max(joinlen, maxLen);

            if (rank[ap] > rank[bp]) {
                parent[bp] = ap;
                len[ap] = joinlen;
            } else if (rank[ap] < rank[bp]) {
                parent[ap] = bp;
                len[bp] = joinlen;
            } else {
                //相同狀況的話,隨便就能夠
                parent[ap] = bp;
                rank[bp]++;
                len[bp] = joinlen;
            }
        }
    }

    public int getPlant() {
        return plant;
    }

    public boolean isJoined(int a, int b) { //兩個節點是不是  鏈接的
        return findP(a) == findP(b);
    }


}
相關文章
相關標籤/搜索