sacredMoonRainTown

导航

并查集学习

模板

class DisjointSet {
public:
    int count = 0;
    vector<int> parent;
    
    DisjointSet(int n) {
        this->count = n;
        parent = vector<int>(n);
        for (int i = 0; i < n; i++) parent[i] = i;
    }

    int find(int p) {
        while (p != parent[p]) {
            parent[p] = find(parent[p]);
            // parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }

    void unions(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;
        parent[rootP] = rootQ;
        --count;
    }
};
  • 解决分群、分类别的问题

题目列表

  • LC.547.朋友圈
  • LC.200.岛屿数量
  • LC.130.被围绕的区域
  • LC.685.冗余连接II(困难)
  • LC.684.冗余连接

LC.200.岛屿数量

  • 除了搜索递归回溯的方法,此题也适用于并查集
  • 简单思路:对所有陆地为1的块进行如上面模板一样的parent[i]=i操作,此处是parent[im+j]=im+j,其中i是行号,j是列号,m是行长度;为0的则输入-1占位,然后再一次遍历,此时只需要判断某位置的下方和右方是否有为1的陆地然后做union操作即可
class DisjointSet {
public:
    int count;
    DisjointSet() {
        count= 0;
    }

    void insert(int i) {
        parent.push_back(i);
        if (i != -1) ++count;
    }

    int find(int p) {
        while (p != parent[p]) {
            parent[p] = find(parent[p]);
            p = parent[p];
        }
        return p;
    }

    void unions(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;
        parent[rootQ] = rootP;
        --count;
    }
};


class Solution {
public:
    int numIslands(vector<vector<char>>& grid)  {
        int n = grid.size();
        if (n <= 0) return 0;
        int m = grid[0].size();
        if (m <= 0) return 0;

        DisjointSet handler;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == '1') handler.insert(i*m+j);
                else handler.insert(-1);
                //cout << handler.count << endl;
            }
        }

        
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == '1') {
                    grid[i][j] = '0';
                    if (i + 1 < n && grid[i+1][j] == '1') handler.unions((i+1)*m + j, i*m+j);
                    if (j + 1 < m && grid[i][j+1] == '1') handler.unions(i*m + j + 1, i*m+j);
                    
                }
            }
        }
        return handler.count;
    }
};

LC.130.被围绕的区域

  • 题意:

  • 思路:并查集方案

    • 引用上面的模板,然后增添一个dumy冗余节点,所有边节点都与之unions,然后所有节点都与其四连通的节点相连,然后最后再遍历一次判断与dumy冗余节点相连与否来确定节点是O还是X
class Solution {
public:
    void solve(vector<vector<char>>& board) {
        int n = board.size();
        if (n <= 0) return;
        int m = board[0].size();
        if (m <= 0) return;

        DisjointSet handler(n*m+1);

        int dumyNode = n*m;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 边界元素与dumyNode连接
                if (board[i][j] == 'O' && (i == 0 || i == n-1 || j == 0 || j == m-1)) {
                    handler.unions(i*m+j, dumyNode);
                }
                // 然后下右判断连接在一起
                if (board[i][j] == 'O') {
                    if (i+1 < n && board[i+1][j] == 'O') {
                        handler.unions((i+1)*m+j, i*m+j);
                    }
                    if (j+1 < m && board[i][j+1] == 'O') {
                        handler.unions(i*m+j, i*m+j+1);
                    }
                }
            } // end for j
        } // end for i

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (board[i][j] == 'O' && handler.find(i*m+j) != handler.find(dumyNode)) {
                    board[i][j] = 'X';
                }
            }
        }
    }
};

LC.685.冗余连接II(困难)

  • 题意:

    • 有向图
    • 有从1——>N的N个节点
    • 给出N条有向边
    • 有根树一般N个节点只需要N-1条边,但给出N条,因此肯定存在“冲突”或者“循环”或者“循环”+“冲突”
    • 任务是找出出现在(给出的有向边的)最后面的删去可以使树合理的边
  • 思路:

    • 经分析,只有三大情况:
    • 1.纯冲突,如1->2,1->3,3->2。此时2有两个父节点,此时删去3->2(删除第二次出现引起冲突的边)
    • 2.纯循环,如1->2,2->3,3->1。此时形成循环,删去3->1(删去属于循环的最后出现的边)
    • 3.循环+冲突,如1->2,2->3,3->1,4->1,此时形成循环+冲突,删去3->1(删去冲突+循环节点在循环中作为dst的那一条边)
  • 实现:

    • 对于纯冲突,可以直接开数组(哈希表,因为是从1——>N,所以直接数组就好了)来存储其父节点编号——>初始化为自身编号,当遇到非自身编号时已证明出现冲突,记录该有向边即可
    • 对于纯循环,使用并查集解决(首先忽略边的有向性,将能“围成环”的节点组成一个“集合”)
      • 因为只有N条边,引起冲突时有两种情况,同时循环或者不循环
        • 不循环那直接记录即可
        • 循环,经过上面的思路分析,最后删除的也是循环内的冲突边,所以直接记录该条冲突循环边即可
    • 这样通过互斥就解决了有向边与并查集的矛盾
class Solution {
public:
    class UnionSet {
    public:
        UnionSet(int n) {
            parent.resize(n+1);
            N = n + 1;
            for (int i = 0; i < n+1; ++i) {
                parent[i] = i;
            }
        }

        int find(int idx) {
            return parent[idx] == idx ? idx: find(parent[idx]);
        }

        void unions(int i, int j) {
            parent[find(i)] = find(j);
        }
    private:
        vector<int> parent;
        int N;
    };

    
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int N = edges.size();
        vector<int> parent(N+1);
        int idx = 0;
        for_each(parent.begin(), parent.end(), [&](int& val){val = idx++;});
        UnionSet us(N);
        int conflictIdx = -1;
        int cycleIdx = -1;

        for (int i = 0; i < N; ++i) {
            if (edges[i][1] != parent[edges[i][1]]) { // 冲突
                conflictIdx = i;
            } else {
                parent[edges[i][1]] = edges[i][0];
                if (us.find(edges[i][0]) == us.find(edges[i][1])) { // 循环
                    cycleIdx = i;
                } else {
                    us.unions(edges[i][0], edges[i][1]);
                }
            }   
        }

        if (conflictIdx == -1) {
            return edges[cycleIdx];
        } else {
            if (cycleIdx == -1) {
                // 先出现的是非循环冲突边,所以当记录冲突的时候将循环边计入循环idx,此时直接返回冲突边即可
                return edges[conflictIdx];
            } else {
                // 先出现的是循环冲突边(此时正常计入parent中形成该边的父子映射),所以当记录冲突的时候将非循环边计入冲突idx,通过冲突idx找到冲突循环节点,然后通过parent数组映射到其在循环内的父节点中即可
                return {parent[edges[conflictIdx][1]], edges[conflictIdx][1]};
            }
        }
        

        vector<int> res(2);
        return res;
    }
};

LC.684.冗余连接

  • 题意:与上一题类似,转换为无向图
  • 因为是无向图,所以就没有冲突的说法,即只需要考虑循环,那么删掉最后一条出现的边即可(利用并查集直接告诉完成)
class UnionSet {
public:
    UnionSet(int len) {
        N = len;
        parent = vector<int>(N);
        for (int i = 0; i < N; i++) parent[i] = i;
    }

    int find(int p) {
        while (p != parent[p]) {
            parent[p] = find(parent[p]);
            p = parent[p];
        }
        return p;
    }

    void unions(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP != rootQ) parent[rootP] = rootQ;
    }

private:
    vector<int> parent;
    int N;
};


class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        int idx;
        int len = edges.size();
        UnionSet us(len + 1);
        for (int i = 0; i < len; ++i) {
            if (us.find(edges[i][0]) == us.find(edges[i][1])) {
                return edges[i];
            } else{
                us.unions(edges[i][0], edges[i][1]);
            }
        }
        return vector<int>(2);
    }
};

posted on 2020-08-07 01:01  sacredMoonRainTown  阅读(64)  评论(0编辑  收藏  举报