并查集总结

本文详细介绍了并查集这一数据结构的原理及其在解决岛屿数量和朋友圈数量问题中的应用。通过并查集实现,可以有效地判断元素是否属于同一集合,并在O(1)的平均时间复杂度下完成集合合并。文章还讨论了在大规模数据下如何通过优化减少初始化开销,例如使用哈希表代替数组,并提供了不同的优化策略实例。
摘要由CSDN通过智能技术生成

原理理解:

并查集在频繁使用(大于或等于O(N))的时候平均时间复杂度可以达到O(1)
1、在合并两个集合的时候使用了小的挂在大的上面
2、在第一次查询代表的时候会将改路径上的所有的节点挂到代表节点上,所以下次查找代表节点会变为O(1)

// 哈希表实现(但是使用的时间比较久,下面会使用数组实现)
package com.atguigu.w;

import java.util.HashMap;
import java.util.List;
import java.util.Stack;

public class Main {

    // 主要用来包装V
    public static class Node<V> {
        V value;

        public Node (V v) {
            value = v;
        }
    }

    public static class UnionFind<V> {
        // 原始的数据和包装后的数据的对应关系
        HashMap<V, Node<V>> nodes;
        // 各个节点对应的父节点
        HashMap<Node<V>, Node<V>> parents;
        // 每个代表节点的节点数量
        HashMap<Node<V>, Integer> sizeMap;

        // 初始化
        public UnionFind(List<V> values) {
            nodes = new HashMap<>();
            parents = new HashMap<>();
            sizeMap = new HashMap<>();
            for (V value : values) {
                Node<V> node = new Node<>(value);
                nodes.put(value,node);
                parents.put(node,node);
                sizeMap.put(node,1);
            }
        }

        // 给你一个节点,请你往上到不能再往上,把代表返回
        public Node<V> findFather(Node<V> cur) {
            Stack<Node<V>> stack = new Stack<>();
            while (cur != parents.get(cur)) {
                stack.push(cur);
                cur = parents.get(cur);
            }
            while (!stack.isEmpty()) {
                parents.put(stack.pop(), cur);
            }
            return cur;
        }

        // 通过比较两个节点的代表节点是否是同一个节点来判断是否在一个集合中
        public boolean isSameSet(V a, V b) {
            return findFather(nodes.get(a)) == findFather(nodes.get(b));
        }

        // 将a、b两个数据所在的集合合并为一个集合(将小的挂到大的上面)
        public void union(V a, V b) {
            Node<V> aHead = findFather(nodes.get(a));
            Node<V> bHead = findFather(nodes.get(b));
            if (aHead != bHead) {
                int aSetSize = sizeMap.get(aHead);
                int bSetSize = sizeMap.get(bHead);
                Node big = aSetSize > bSetSize ? aHead : bHead;
                Node small = big == aHead ? bHead : aHead;
                parents.put(small,big);
                sizeMap.put(big, aSetSize + bSetSize);
                sizeMap.remove(small);
            }
        }
        // 返回最后的集合个数
        public int sets() {
            return sizeMap.size();
        }
    }
}

练习

547 省份数量
package com.atguigu.w;

// 本题为leetcode原题
// 测试链接:https://leetcode.com/problems/friend-circles/
// 可以直接通过
public class Main {

    public static int findCircleNum(int[][] M) {
        int m = M.length;
        UnionFind unionFind = new UnionFind(m);
        // 只遍历右上角
        for (int i = 0; i < m; i++) {
            for (int j = i + 1; j < m; j++) {
                // M[i][j] == 1,表示认识,所有合并
                if (M[i][j] == 1) {
                    unionFind.union(i,j);
                }
            }
        }
        return unionFind.sets;

    }

    public static class UnionFind {
        // 用数组代替HashMap
        // parent[i] = k : i的父亲是k
        public int[] parents;
        // size[i] = k : 如果i是代表节点,size[i]才有意义,否则无意义
        // i所在的集合大小是多少
        public int[] size;
        // 用数组代替栈
        public int[] help;
        // 记录集合数量
        public int sets;

        public UnionFind(int N) {
            parents = new int[N];
            size = new int[N];
            help = new int[N];
            for (int i = 0; i < N; i++) {
                parents[i] = i;
                size[i] = 1;
            }
        }

        private int find(int i) {
            int hi = 0;
            while (i != parents[i]) {
                help[hi++] = i;
                i = parents[i];
            }
            for (hi--; hi > 0; hi--) {
                parents[help[hi]] = i;
            }
            return i;
        }

        public void union(int i, int j) {
            int f1 = find(i);
            int f2 = find(j);
            if (f1 != f2) {
                if (size[f1] > size[f2]) {
                    size[f1] += size[f2];
                    parents[f2] = f1;
                } else {
                    size[f2] += size[f1];
                    parents[f1] = f2;
                }
                sets--;
            }
        }

        public int sets() {
            return sets;
        }
    }

}
200 岛屿数量
package com.atguigu.w;

// 本题为leetcode原题
// 测试链接:https://leetcode.com/problems/friend-circles/
// 可以直接通过
public class Main {

    // 方式一:使用感染的方式,依次遍历每个岛,检查每个岛的上下左右
    public static int numIsland (char[][] board) {
        int islands = 0;
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] == '1') {
                    islands++;
                    infect(board,i,j);
                }
            }
        }
        return islands;
    }

    public static void infect (char[][] board, int i, int j) {
        if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] != '1') {
            return;
        }
        board[i][j] = 2;
        infect(board,i - 1,j);
        infect(board,i + 1,j);
        infect(board, i, j - 1);
        infect(board,i, j + 1);
    }

}
package com.atguigu.w;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;

class Main {
    // 方式二:使用并查集的方式(HashMap)
    public static int numIslands(char[][] board) {
        int row = board.length;
        int col = board[0].length;
        Dot[][] dots = new Dot[row][col];
        List<Dot> dotList = new ArrayList<>();
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (board[i][j] == '1') {
                    dots[i][j] = new Dot();
                    dotList.add(dots[i][j]);
                }
            }
        }
        UnionFind<Dot> uf = new UnionFind<>(dotList);
        // 第一行从第二个开始如果自己和左边都为1则合并
        for (int i = 1; i < col; i++) {
            if (board[0][i - 1] == '1' && board[0][i] == '1') {
                uf.union(dots[0][i - 1],dots[0][i]);
            }
        }
        // 第一列从第二行开始如果自己和上边都为1则合并
        for (int i = 1; i < row; i++) {
            if (board[i - 1][0] == '1' && board[i][0] == '1') {
                uf.union(dots[i - 1][0],dots[i][0]);
            }
        }
        // 其余部分如果自己和自己左边和上边都为1则合并
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < col; j++) {
                if (board[i][j] == '1') {
                    if (board[i][j - 1] == '1') {
                        uf.union(dots[i][j - 1],dots[i][j]);
                    }
                    if (board[i - 1][j] == '1') {
                        uf.union(dots[i - 1][j],dots[i][j]);
                    }
                }
            }

        }
        return uf.sets();
    }

    // 使相同的1的地址不同
    public static class Dot {

    }

    public static class Node<V> {
        V value;

        public Node(V v) {
            value = v;
        }
    }

    public static class UnionFind<V> {
        public HashMap<V, Node<V>> nodes;
        public HashMap<Node<V>, Node<V>> parents;
        public HashMap<Node<V>, Integer> sizeMap;

        public UnionFind(List<V> values) {
            nodes = new HashMap<>();
            parents = new HashMap<>();
            sizeMap = new HashMap<>();
            for (V cur : values) {
                Node<V> node = new Node(cur);
                nodes.put(cur,node);
                parents.put(node,node);
                sizeMap.put(node,1);
            }
        }

        public Node<V> findFather(Node<V> cur) {
            Stack<Node<V>> path = new Stack<>();
            while (cur != parents.get(cur)) {
                path.push(cur);
                cur = parents.get(cur);
            }
            while (!path.isEmpty()) {
                parents.put(path.pop(),cur);
            }
            return cur;
        }

        public void union(V a, V b) {
            Node<V> aHead = findFather(nodes.get(a));
            Node<V> bHead = findFather(nodes.get(b));
            if (aHead != bHead) {
                int aSetSize = sizeMap.get(aHead);
                int bSetSize = sizeMap.get(bHead);
                Node<V> big = aSetSize >= bSetSize ? aHead : bHead;
                Node<V> small = big == aHead ? bHead : aHead;
                parents.put(small,big);
                sizeMap.put(big,aSetSize + bSetSize);
                sizeMap.remove(small);
            }
        }

        public int sets() {
            return sizeMap.size();
        }
    }
}
package com.atguigu.w;

import java.util.ArrayList;
import java.util.List;

class Main {
    // 方式三:使用并查集的方式(数组)
    public static int numIslands(char[][] board) {
        int row = board.length;
        int col = board[0].length;
        UnionFind uf = new UnionFind(board);
        for (int i = 1; i < col; i++) {
            if (board[0][i - 1] == '1' && board[0][i] == '1') {
                uf.union(0,i - 1,0,i);
            }
        }
        for (int i = 1; i < row; i++) {
            if (board[i - 1][0] == '1' && board[i][0] == '1') {
                uf.union(i - 1,0,i,0);
            }
        }
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < col; j++) {
                if (board[i][j] == '1') {
                    if (board[i][j - 1] == '1') {
                        uf.union(i,j-1,i,j);
                    }
                    if (board[i -1][j] == '1') {
                        uf.union(i-1,j,i,j);
                    }
                }
            }
        }
        return uf.sets();
    }


    public static class UnionFind {
        private int[] parents;
        private int[] size;
        private int[] help;
        private int col;
        private int sets;

        public UnionFind(char[][] board) {
            col = board[0].length;
            sets = 0;
            int row = board.length;
            int len = row * col;
            parents = new int[len];
            size = new int[len];
            help = new int[len];
            for (int r = 0; r < row; r++) {
                for (int c = 0; c < col; c++) {
                    if (board[r][c] == '1') {
                        int i = index(r,c);
                        parents[i] = i;
                        size[i] = 1;
                        sets++;
                    }
                }
            }
        }

        public int index(int r, int c) {
            return r * col + c;
        }

        public int find(int i) {
            int hi = 0;
            while (i != parents[i]) {
                help[hi++] = i;
                i = parents[i];
            }
            for (hi--; hi >= 0; hi--) {
                parents[help[hi]] = i;
            }
            return i;
        }

        public void union(int r1, int c1, int r2, int c2) {
            int i1 = index(r1, c1);
            int i2 = index(r2, c2);
            int f1 = find(i1);
            int f2 = find(i2);
            if (f1 != f2) {
                if (size[f1] > size[f2]) {
                    size[f1] += size[f2];
                    parents[f2] = f1;
                } else {
                    size[f2] += size[f1];
                    parents[f1] = f2;
                }
                sets--;
            }
        }

        public int sets() {
            return sets;
        }
    }
}
305 岛屿数量
package com.atguigu.w;

import java.util.ArrayList;
import java.util.List;

// 本题为leetcode原题
// 测试链接:https://leetcode.com/problems/number-of-islands-ii/
// 所有方法都可以直接通过
class Main {
	// 在开始的时候设置为0,每次变为1后都使用上下左右感染并且返回集合的数量
    public List<Integer> numIslands2(int m, int n, int[][] positions) {
        UnionFind uf = new UnionFind(m,n);
        List<Integer> ans = new ArrayList<>();
        for (int[] position : positions) {
            ans.add(uf.connect(position[0],position[1]));
        }
        return ans;
    }


    public static class UnionFind {
        private int[] parents;
        private int[] size;
        private int[] help;
        private int sets;
        private final int row;
        private final int col;

        public UnionFind (int m, int n) {
            row = m;
            col = n;
            sets = 0;
            int len = row * col;
            parents = new int[len];
            size = new int[len];
            help = new int[len];
        }

        private int index (int r, int c) {
            return r * col + c;
        }

        private int find(int i) {
            int hi = 0;
            while (i != parents[i]) {
                help[hi++] = i;
                i = parents[i];
            }
            for (hi--; hi >= 0; hi--) {
                parents[help[hi]] = i;
            }
            return i;
        }

        private 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 i1 = index(r1, c1);
            int i2 = index(r2, c2);
            if (size[i1] == 0 || size[i2] == 0) {
                return;
            }
            int f1 = find(i1);
            int f2 = find(i2);
            if (f1 != f2) {
                if (size[f1] >= size[f2]) {
                    size[f1] += size[f2];
                    parents[f2] = f1;
                } else {
                    size[f2] += size[f1];
                    parents[f1] = f2;
                }
                sets--;
            }
        }

        public int connect(int r, int c) {
            int index = index(r, c);
            if (size[index] == 0) {
                size[index] = 1;
                parents[index] = index;
                sets++;
                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 sets;
        }

    }
}

上一题中如果m*n比较大,会经历很重的初始化,而k比较小,怎么优化

package com.atguigu.w;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

class Main {

    public static List<Integer> numIslands2(int m, int n, int[][] positions) {
        UnionFind uf = new UnionFind();
        List<Integer> ans = new ArrayList<>();
        for (int[] position : positions) {
            ans.add(uf.connect(position[0],position[1]));
        }
        return ans;
    }

    public static class UnionFind {
    	// 利用HashMap<String,String>代替int[]
        private HashMap<String, String> parents;
        private HashMap<String, Integer> size;
        private ArrayList<String> help;
        private int sets;

        public UnionFind() {
            parents = new HashMap<>();
            size = new HashMap<>();
            help = new ArrayList<>();
            sets = 0;
        }

        private String find (String cur) {
            while (!cur.equals(parents.get(cur))) {
                help.add(cur);
                cur = parents.get(cur);
            }
            for (String str : help) {
                parents.put(str,cur);
            }
            help.clear();
            return cur;
        }

        private void union(String s1, String s2) {
            if (parents.containsKey(s1) && parents.containsKey(s2)) {
                String f1 = find(s1);
                String f2 = find(s2);
                if (!f1.equals(f2)) {
                    Integer size1 = size.get(f1);
                    Integer size2 = size.get(f2);
                    String big = size1 > size2 ? f1 : f2;
                    String small = big == f1 ? f2 : f1;
                    parents.put(small,big);
                    size.put(big,size1 + size2);
                    sets--;
                }
            }
        }

        private int connect(int r, int c) {
            String key = String.valueOf(r) + "_" + String.valueOf(c);
            if (!parents.containsKey(key)) {
                parents.put(key,key);
                size.put(key,1);
                sets++;
                String up = String.valueOf(r - 1) + "_" + String.valueOf(c);
                String down = String.valueOf(r + 1) + "_" + String.valueOf(c);
                String left = String.valueOf(r) + "_" + String.valueOf(c - 1);
                String right = String.valueOf(r) + "_" + String.valueOf(c + 1);
                union(up,key);
                union(down,key);
                union(left,key);
                union(right,key);
            }
            return sets;
        }


        public static void main(String[] args) {
            int[][] positions = {{0,0},{0,1},{1,2},{2,1}};
            List<Integer> integerList = numIslands2(3, 3, positions);
            System.out.println(integerList);
        }

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值