并查集

早过忘川 提交于 2019-12-04 16:43:43

并查集概念

并查集是一种用来管理元素分组情况的数据结构。并查集可以高效地进行如下操作。

  • 查询元素a和元素b是否属于同一组。
  • 合并元素a和元素b所在的组。

    模板

    1234567891011121314151617181920212223242526272829303132333435
    class {private:    vector<int> parent, rank;public:    DisjointSets(int num) {        parent = vector<int>(num, 0);        rank = vector<int>(num, 0);        for (int i = 0; i < num; i++) {            parent[i] = i;        }    }    int find(int p) {        if(p == parent[p])            return p;        parent[p] = find(parent[p]);        return parent[p];    }    void unionTwo(int p, int q) {        int rootP = find(p);        int rootQ = find(q);        if (rootP == rootQ) return;        if (rank[rootQ] > rank[rootP]) {            parent[rootP] = rootQ;        }else {            parent[rootQ] = rootP;            if (rank[rootP] == rank[rootQ]) {                rank[rootP]++;            }        }    }  };

Disjoint Sets (以下简称DS)的本质是许多棵树。每个圈子里的元素都组成了一棵树。然而我们的表示方法并不是用常规tree node的方法记录的,而是用数组.

DS里面拥有一个大小为N的vector parent,对于parent[i],存储着第i号元素所属圈子的老大。DS里面的另一个vector rank用来存储树的高度,它的作用会在Union操作中体现出来。

DS里面经典的两个操作分别是find和Union.

find

find就是寻找这个圈子的老大(这棵树的root)。对于find来说,在寻找root的过程中,我们不仅要找到root,还要把沿途经过的所有node的parent都变成root,也就是把自己和沿途所有的node都变成root的孩子(同时也变成了深度为1的leaf)。这个操作叫path compression,意义在于下次我们如果寻找这些node的老大,就可以一步到位了(直接通过parent[i]找到root)。这对时间复杂度的优化是非常重要的。

Union

Union是什么?是我们知道了两个元素属于同一个圈子。如果我们的DS已经知道这一点,那么没问题,如果我们的DS不知道这一点,我们要告诉它。并且很重要的是,这里不是针对两个元素,而是要把这两个元素所处的两个圈子融合成一个圈子,这也就是Union叫法的由来。那么我们分别用find找到两个元素所处tree的root,然后通过比较rank[root]的大小看哪棵树更大。最后我们把小的那棵树的root变成大的那棵树的root的孩子。之所以要选择把小的那棵树融合进大的那棵树,是因为我们希望让树的孩子的高度都尽量小。假设小的树的孩子数量是N1,大的树的孩子数量是N2。如果把小数融合进大树,那么我们让N1个node的高度都增加了1,反之我们要让N2个node的高度都增加1。后者明显是违反我们希望让树的孩子的高度都尽量小这个意愿的。
Disjoint Sets using union by rank and path compression Graph Algorithm中以具体的例子演示了Union的过程。

例题

Friend Circles

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
class Solution {public:    class {    private:        vector<int> parent, rank;        int count;    public:        DisjointSets(int num) {            count = num;            parent = vector<int>(num, 0);            rank = vector<int>(num, 0);            for (int i = 0; i < num; i++) {                parent[i] = i;            }        }        int find(int p) {            if(p == parent[p])                return p;            parent[p] = find(parent[p]);            return parent[p];        }        void unionTwo(int p, int q) {            int rootP = find(p);            int rootQ = find(q);            if (rootP == rootQ) return;            count -= 1;            if (rank[rootQ] > rank[rootP]) {                parent[rootP] = rootQ;            }else {                parent[rootQ] = rootP;                if (rank[rootP] == rank[rootQ]) {                    rank[rootP]++;                }            }        }          int getCount() {            return count;        }    };    int findCircleNum(vector<vector<int>>& M) {        DisjointSets ds(M.size());      大专栏  并查集   for (int i = 0, n = M.size(); i < n; ++i) {            for (int j = 0; j < i; ++j) {                if (M[i][j]) ds.unionTwo(i, j);            }        }        return ds.getCount();    }};

对于整个matrix,如果M[i][j]为1,我们则需要告诉DS i和j属于一个圈子。这里因为对称性,我们只需要遍历半个matrix就可以得到所有信息。在DS里面我增加了一个count。count代表现在DS中独立圈子的数量。因为最开始我们什么信息都不知道,假设每个人都属于自己的独立圈子,所以count为人的个数,之后每一次Union操作,如果我们发现i和j所处圈子(的老大)不同(root1 != root2),那么说明有两个圈子要合并成一个,那么就少了一个圈子,所以count要减1.最后我们返回count,也就是圈子的数量。

Number of Islands

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
class Solution {public:    class {    private:        vector<int> parent, rank;        int count;    public:        DisjointSets(int num) {            count = num;            parent = vector<int>(num, 0);            rank = vector<int>(num, 0);            for (int i = 0; i < num; i++) {                parent[i] = i;            }        }        int find(int p) {            if(p == parent[p])                return p;            parent[p] = find(parent[p]);            return parent[p];        }        void unionTwo(int p, int q) {            int rootP = find(p);            int rootQ = find(q);            if (rootP == rootQ) return;            count -= 1;            if (rank[rootQ] > rank[rootP]) {                parent[rootP] = rootQ;            }else {                parent[rootQ] = rootP;                if (rank[rootP] == rank[rootQ]) {                    rank[rootP]++;                }            }        }          int getCount() {            return count;        }    };        int numIslands(vector<vector<char>>& grid) {        if(grid.size() == 0 || grid[0].size() ==0)            return 0;        int m = grid.size();        int n = grid[0].size();        int num = 0;        unordered_map<int, int> hashMap;        for(int i = 0; i < m; ++i){            for(int j = 0; j < n; ++j){                if(grid[i][j] == '1'){                    hashMap[i*n+j] = num;                    num++;                }            }        }        DisjointSets ds(num);           for(int i = 0; i < m; ++i){            for(int j = 0; j < n; ++j){                if(grid[i][j] == '1'){                    if(i - 1 >= 0 && hashMap.count((i-1)*n+j)){                        ds.unionTwo(hashMap[(i-1)*n+j], hashMap[i*n+j]);                    }                    if(i + 1 < m && hashMap.count((i+1)*n+j)){                        ds.unionTwo(hashMap[(i+1)*n+j], hashMap[i*n+j]);                    }                    if(j - 1 >= 0 && hashMap.count(i*n+j-1)){                        ds.unionTwo(hashMap[i*n+j-1], hashMap[i*n+j]);                    }                    if(j + 1 < n && hashMap.count(i*n+j+1)){                        ds.unionTwo(hashMap[i*n+j+1], hashMap[i*n+j]);                    }                }            }        }        return ds.getCount();    }};

参考资料:

  1. 超有爱的并查集~
  2. Disjoint Sets using union by rank and path compression Graph Algorithm
  3. Disjoint Sets / Union Find
  4. Union Find Kruskal’s Algorithm
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!