并查集的应用及常见改写

1.朋友圈问题

假设给你一个二维数组图中为1的位置为互相认识,该二维数组根据左对角线对称 ,{2,0}位置是1,说明2和0认识,{0,2},{0,4}位置为1说明0除了和2认识之外,还和4认识,虽然 2 和 4 互相不认识,但他们都和 0 认识,所以 {0,2,4} 是一个朋友圈,该问题是给你一个二维数组,求一共有多少个朋友圈?

分析:这是一个很典型的并查集的问题,0,1,2,3,4自己本来就认识自己,可以当做一个集合,假如说0和1认识,就合并0和1  将所有的合并完之后集合的数量就是该题朋友圈的数量。该题需要对经典并查集改写,该题是采用数组作为并查集来实现的。当然使用HashMap也可以,但是,使用HashMap它没有数组寻址速度快,所以我们采用数组来实现。因为该二维数据关于左对角线对称,所以我们只遍历右上角就行。

代码如下:

public class FriendCircle {

    private int[] parent;
    private int[] size;
    private int[] help;
    private Integer seats;

    public FriendCircle(int N){
        //parent[i] = k 说明 i位置的父亲是 k
        parent = new int[N];
        //size[i] = k 如果i是代表节点,size[i]才有意义,不是代表节点无意义,如果有意义,表示该代表节点所在集合大小为k
        size = new int[N];
        //辅助数组,用于压缩路径
        help = new int[N];
        //一个有多少个集合
        seats = N;
        for (int i = 0; i < N; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }
    //一直往上,往上到不能再往上,是代表节点
    //同时要做路径压缩
    public int findFather(int a){
        int index = 0;
        while (a != parent[a]){
            help[index] = a;
            a = parent[a];
            index++;
        }
        while (index > 0){
            parent[help[--index]] = a;
        }
        return a;
    }

    public void union(int a,int b){
        int aHead = findFather(a);
        int bHead = findFather(b);
        if (aHead != bHead){
                if (size[aHead] >= size[bHead]){
                    size[aHead] += size[bHead];
                    parent[bHead] = aHead;
                }else {
                    size[bHead] += size[aHead];
                    parent[aHead] = bHead;
                }
                seats--;
            }
    }

    public static int findCircleNum(int[][] m){
        int N = m.length;
        FriendCircle friendCircle = new FriendCircle(N);
        for (int i = 0; i < N; i++) {
            for (int j = i+1; j < N; j++) {
                //i 和 j 互相认识, i所在的集合 和 j 所在的集合合并
                if (m[i][j] == 1){
                    friendCircle.union(i,j);
                }
            }
        }
        return friendCircle.seats;
    }

}

2.岛问题

给定一个二维数组matrix,里面的值不是 1 就是  0。上下左右相邻的 1 视一片岛,返回matrix中岛的数量。

利用递归实现:

当我们发现一个位置是'1'时,我们就把他的上下左右都改成0,我们看一共调了多少次infect方法,就是有多少个岛。

public static int numIsland(char[][] arr){
        int isLand = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[0].length; j++) {
                if (arr[i][j] == '1'){
                    infect(arr,i,j);
                    isLand++;
                }
            }
        }
        return isLand;
    }

    //从 i,j 这个位置出发,把所有'1'位置变成0
    public static void infect(char[][] arr,int i,int j){
        if (i < 0 || j < 0 || i == arr.length || j== arr[0].length || arr[i][j] != '1'){
            return;
        }
        arr[i][j] = 0;
        infect(arr,i,j-1);
        infect(arr,i,j+1);
        infect(arr,i-1,j);
        infect(arr,i+1,j);
    }

下面介绍一下使用并查集来实现,其实这道题用递归来实现已经是最优解了,那为啥还要用并查集来实现呢,其一就是可以练习并查集,其二 岛问题的加强版就只能用并查集来实现,用递归就不行了,首先 ,使用并查集实现如下:

利用并查集实现:

根据第一题我们知道,并查集都是一维的,那这题我们给的是二维数组,该怎么办呢?

我们可以把它变成一维的,在我们想象的空间中,我们把第二列拼到第一列后面,假如说一共有 3行4列,第二行第二列,转换成一维就是 1 * 4 + 2 = 6  1是第二行下标为  1,4 是一共四列  , 2 是第二列。这样就可以把一个二维数组转换为一维数据,当然,这么写的话当行数过大时,可能会有溢出风险,但如果我们的数据量真到达那么大的时候,我们可以再想其他方法。譬如将并查集改为二维数组的形式,不过该题还是用一维数组来实现。

public  class UnionFind{
        private  int[] parent;
        private  int[] size;
        private  int[] help;
        private  int col;
        private  int sits;

        public UnionFind(char[][] arr){
            int row = arr.length;
            col = arr[0].length;
            int N = row * col;
            parent = new int[N];
            size = new int[N];
            help = new int[N];
            sits = 0;
            for (int i = 0; i < arr.length; i++) {
                for (int j = 0; j < arr[0].length; j++) {
                    if (arr[i][j] == '1'){
                        int index = index(i, j);
                        parent[index] = index;
                        size[index] = 1;
                        sits++;
                    }
                }
            }
        }

        public  int index(int i,int j){
            return i * col + j;
        }

        //原始位置下标
        private  int find(int a){
            int index = 0;
            while (a != parent[a]){
                help[index] = a;
                a = parent[a];
                index++;
            }
            while (index > 0){
                parent[help[--index]] = a;
            }
            return a;
        }

        public  void union(int a1,int a2,int b1,int b2){
            int aHead = find(index(a1,a2));
            int bHead = find(index(b1,b2));
            if (aHead != bHead){
                if (size[aHead] >= size[bHead]){
                    size[aHead] += size[bHead];
                    parent[bHead] = aHead;
                }else {
                    size[bHead] += size[aHead];
                    parent[aHead] = bHead;
                }
                sits--;
            }
        }
    }



    public int numIsLandUnionFind(char[][] grid){
        

        UnionFind unionFind = new UnionFind(grid);
        int col = grid[0].length;
        int row = grid.length;
        for (int i = 1; i < col; i++) {
            if (grid[0][i] == '1' && grid[0][i-1] == '1'){
                unionFind.union(0,i,0,i-1);
            }
        }
        for (int i = 1; i < row; i++) {
            if (grid[i][0] == '1' && grid[i-1][0] == '1'){
                unionFind.union(i,0,i-1,0);
            }
        }
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < col; j++) {
                if (grid[i][j] == '1'){
                    if (grid[i-1][j] == '1'){
                        unionFind.union(i-1,j,i,j);
                    }
                    if (grid[i][j-1] == '1'){
                        unionFind.union(i,j-1,i,j);
                    }
                }
            }
        }
        return unionFind.sits;
    }

岛问题 II

给一个m和一个n。 一个position{[0,0],[0,1],[1,2],[2,1]}

m 和n 形成一个二维的数组,初始全为0, 第一个position[0,0] 二维数组中 0,0 位置变为 1 这是有 几个岛? 1 个  第二个position[0,1]  数组中 [0,1]位置变1 这是还是1个岛,第三个position[1,2],二维数组中[1,2]变1,这是数组中有两个岛,第四个position[2,1],数组中[2,1]位置变1,这时候数组中有3个岛,需要输出的就是以上每个位置有多少个岛。

这道题就不能用递归来写了,用并查集是最好的写法。因为并查集有一个很重要的技巧就是动态连接,我们每空降一个位置,就动态的初始化去连接。

使用并查集实现:

public class UnionFind2{
        private int[] parent;
        private int[] size;
        private int[] help;
        private int col;
        private int row;
        private int sits;
        public UnionFind2(int m,int n){
            parent = new int[m * n];
            size = new int[m * n];
            help = new int[m * n];
            col = n;
            row = m;
            sits = 0;
        }

        public int find(int num){
            int index = 0;
            while (num != parent[num]){
                help[index++] = num;
                num = parent[num];
            }
            while (index > 0){
                parent[help[--index]] = num;
            }
            return num;
        }
        public int index(int m,int n){
            return m * col + n;
        }

        public void union(int r1,int c1,int r2,int c2){
            if (r1 < 0 || r1 == row || r2 < 0 || r2 == row || c1 < 0 || c1 ==col || c2 < 0 || c2 == col){
                return;
            }
            int aPosition = index(r1,c1);
            int bPosition = index(r2,c2);
            if (size[aPosition] == 0 || size[bPosition] == 0){
                return;
            }
            int f1 = find(aPosition);
            int f2 = find(bPosition);
            if (f1 != f2){
                if (size[f1] >= size[f2]){
                    size[f1] += size[f2];
                    parent[f2] = f1;
                }else {
                    size[f2] += size[f1];
                    parent[f1] = f2;
                }
                sits--;
            }
        }
        public int connet(int r,int c){
            int index = index(r,c);
            if (size[index] == '0'){
                parent[index] = index;
                size[index] = 1;
                sits++;
                union(r-1,c,r,c);
                union(r+1,c,r,c);
                union(r,c-1,r,c);
                union(r,c+1,r,c);
            }
            return sits;
        }
    }


    public  List<Integer> numIsLand2(int m,int n,int[][] position){
        UnionFind2 unionFind2 = new UnionFind2(m,n);
        List<Integer> list = new ArrayList<>();
        for (int[] ints : position) {
            list.add(unionFind2.connet(ints[0],ints[1]));
        }
        return list;
    }

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值