高级数据结构之并查集

高级数据结构之并查集

1.并查集介绍

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
初始时每个元素各位树根
逐个按边的关系进行合并

2.并查集核心方法

并查集核心方法

//有两种设置初始值方法
//1.全设置为-1,但不方便路径压缩, parent[i] = -1;
//2.设置为相应坐标值,parent[i] = i;
int[] parent;

public find(int x){
//查找x的树根
//优化
//路径压缩 自顶向下更新parent中的树根
}
public union(int x, int y){
//进行树根合并
//合并方法优化 扁平化合并
//1.设置一个rank[]数组,按秩合并
//2.设置size[]数组,按节点合并
}

如下代码判断是否有环,合并失败则有同一个树根证明有环

public class DisjoinSet {
    static final int N = 6;

    public int find(int x, int[] parent){
        if(parent[x] != -1){
            x = find(parent[x], parent);
        }
        return x;
    }

    public boolean union(int x, int y, int[] parent){
        int x_root = find(x, parent);
        int y_root = find(y, parent);
        if(x_root == y_root)return false;
        //x的根为y
        parent[x_root] = y_root;
        return true;
    }

    @Test
    public void test(){
        int[] parent = new int[N];
        Arrays.fill(parent, -1);
        int[][] matrix = {{0, 1}, {1, 2}, {1, 3}, {2, 5}, {3, 4}, {2, 4}};
        int[][] edges = {{0, 1}, {1, 2}, {1, 3}, {2, 5}, {3, 4}};
//        for(int i = 0; i < N; i++){
//            int x = matrix[i][0];
//            int y = matrix[i][1];
//            if(!union(x, y, parent)){
//                System.out.println("Cycle existed!");
//                break;
//            }
//        }

        for(int i = 0; i < N - 1; i++){
            int x = edges[i][0];
            int y = edges[i][1];
            if(!union(x, y, parent)) {
                System.out.println("Cycle existed!");
            }
        }
        System.out.println("UnCycle");

    }

3.优化

1.秩合并

当union的节点都是线性的,那么从0,1000都是一条直线,那么当查找0的根节点时,他需要遍历1000次,效率比较低

现在设置新的数组秩,按秩进行一个合并,也就是按树高来进行合并,谁的树高,谁的树根就是新的树根,当两个树根相等的时候,随便选一个,但是同时将新的根的那棵树的树高加1

2.路径压缩

public int find(int x){
            if(parent[x] == x)return x;
            while(parent[x] != x){
            	//路径压缩优化
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }

优化后的判断树是否有环

public class DisjoinSet {
    static final int N = 6;

    public int find(int x, int[] parent, int[] rank){
        if(parent[x] != -1){
            x = find(parent[x], parent, rank);
        }
        return x;
        
        //return x == parent[x] ? x : find(parent[x], paren)
    }

    public boolean union(int x, int y, int[] parent, int[] rank){
        int x_root = find(x, parent, rank);
        int y_root = find(y, parent, rank);
        if(x_root == y_root)return false;
        if(rank[x_root] < rank[y_root]){
            parent[x_root] = y_root;
        }else if(rank[x_root] > rank[y_root]){
            parent[y_root] = x_root;
        }else{
            parent[x_root] = y_root;
            rank[y_root]++;
        }
        return true;
    }

    @Test
    public void test(){
        int[] parent = new int[N];
        int[] rank = new  int[N];
        Arrays.fill(rank, 1);
        Arrays.fill(parent, -1);
        int[][] edges = {{0, 1}, {1, 2}, {1, 3}, {2, 5}, {3, 4}}
        for(int i = 0; i < N - 1; i++){
            int x = edges[i][0];
            int y = edges[i][1];
            if(!union(x, y, parent, rank)) {
                System.out.println("Cycle existed!");
            }
        }
        System.out.println("UnCycle");

    }
}

扁平化的策略有rank控制树高,和size获取树节点个数

4.LeetCode并查集

128. 最长连续序列

 	HashMap<Integer, Integer> find = new HashMap<>();
    HashMap<Integer, Integer> rank = new HashMap<>();
    int max = 1;
    public int longestConsecutive(int[] nums) {
        if(nums.length == 0 || nums == null)return 0;
        for(int num : nums){
        //初始化
            find.put(num, num);
            rank.put(num, 1);
        }

        for(int num : nums){
            if(find.containsKey(num - 1)){
                union(num, num - 1);
            }
        }
        return max;
    }
    public int unionFind(int x){
        int x_root = find.get(x);
        if(x_root != x){
            x_root = unionFind(x_root);
        }
        find.put(x, x_root);
        return x_root;
    }

    public void union(int x, int y){
        int x_root = unionFind(x);
        int y_root = unionFind(y);
        if(x_root == y_root)return;
        int x_rank = rank.get(x_root);
        int y_rank = rank.get(y_root);
        if(x_rank > y_rank){
            find.put(y_root, x_root);
            rank.put(x_root, x_rank + y_rank);
        }else{
            find.put(x_root, y_root);
            rank.put(y_root, x_rank + y_rank);
        }
        max = Math.max(max, x_rank + y_rank);
    }

261. 以图判树

	int[] parent;
    int[] rank;
    int count;
    public boolean validTree(int n, int[][] edges) {
        parent = new int[n];
        rank = new int[n];
        Arrays.fill(parent, -1);
        Arrays.fill(rank, 1);
        count = n;
        for(int i = 0; i < edges.length; i++){
            int x = edges[i][0];
            int y = edges[i][1];
            if(!union(x, y))return false;
        }
        return count == 1;
    }

    public int find(int x){
        return parent[x] == -1 ? x : find(parent[x]);
    }

    public boolean union(int x, int y){
        int x_root = find(x);
        int y_root = find(y);
        if(x_root == y_root)return false;

        if(rank[x_root] > rank[y_root]){
            parent[y_root] = x_root;
        }else if(rank[y_root] > rank[x_root]){
            parent[x_root] = y_root;
        }else{
            parent[x_root] = y_root;
            rank[y_root]++;
        }
        count -= 1;
        return true;
    }

684. 冗余连接

public int[] findRedundantConnection(int[][] edges) {
        int n = edges.length;
        UnionFind f = new UnionFind(n);
        for(int i = 0; i < n; i++){
            int x = edges[i][0];
            int y = edges[i][1];
            if(!f.union(x, y))return edges[i];
        }

        return new int[]{-1, -1};
    }

    class UnionFind{
        int[] parent;
        int[] rank;
        public UnionFind(int n){
            parent = new int[n + 1];
            rank = new int[n + 1];
            for(int i = 1; i < n + 1; i++){
                parent[i] = i;
                rank[i] = 1;
            }
        }

        public boolean union(int x , int y){
            int x_root = find(x);
            int y_root = find(y);
            if(x_root == y_root)return false;
            if(rank[x_root] > rank[y_root]){
                parent[y_root] = x_root;
            }else if(rank[x_root] < rank[y_root]){
                parent[x_root] = y_root;
            }else{
                parent[x_root] = y_root;
                rank[y_root]++;
            }
            return true;
        }

        public int find(int x){
            if(parent[x] == x)return x;
            while(parent[x] != x){
            	//路径压缩优化
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }
    }

721. 账户合并

public List<List<String>> accountsMerge(List<List<String>> accounts) {
        int n = accounts.size();
        HashMap<String, Integer> emailToId = new HashMap<>();
        UnionFind f = new UnionFind(n);
        for(int i = 0; i < n; i++){
            List<String> list = accounts.get(i);
            //先不管帐户名只记id
            for(int j = 1; j < list.size(); j++){
                String email = list.get(j);
                if(!emailToId.containsKey(email)){
                    emailToId.put(email, i);
                }else{
                    //如果map里有了地址,那么肯定有同名账户,则进行合并
                    f.union(i, emailToId.get(email));
                }
            }
        }
        //记录根节点id对应的email
        HashMap<Integer, List<String>> idToEmail = new HashMap<>();
        for(String email : emailToId.keySet()){
            int id = emailToId.get(email);
            //找到根节点id
            int root = f.find(id);
            idToEmail.computeIfAbsent(root, z -> new ArrayList<>()).add(email);
        }
        //根据id到账户中取出帐户名,对结果进行处理
        List<List<String>> res = new ArrayList<>();
        for(int id : idToEmail.keySet()){
            String name = accounts.get(id).get(0);
            List<String> list = idToEmail.get(id);
            Collections.sort(list);
            list.add(0, name);
            res.add(list);
        }
        return res;
    }

    class UnionFind{
        int[] parent;
        int[] rank;

        public UnionFind(int n){
            parent = new int[n];
            rank = new int[n];
            Arrays.fill(parent, -1);
            Arrays.fill(rank, 1);
        }

        public int find(int x){
            return parent[x] == -1 ? x : find(parent[x]);
        }

        public void union(int x, int y){
            int x_root = find(x);
            int y_root = find(y);
            if(x_root == y_root)return;
            if(rank[x_root] > rank[y_root]){
                parent[y_root] = x_root;
            }else if(rank[y_root] > rank[x_root]){
                parent[x_root] = y_root;
            }else{
                parent[x_root] = y_root;
                rank[y_root]++;
            }
        }
    }

765. 情侣牵手

public int minSwapsCouples(int[] row) {
        int len = row.length;
        int n = len / 2;
    	//索引对d
        UnionFind f = new UnionFind(n);
        for(int i = 0; i < len; i += 2){
            //情侣对的索引在除以2时是一样的
            f.union(row[i] / 2, row[i + 1] / 2);
        }
        return n - f.count;
    }

    class UnionFind{
        int[] parent;
        int count;
        public UnionFind(int n){
            parent = new int[n];
            for(int i = 0; i < n; i++){
                parent[i] = i;
            }
            count = n;
        }

        public void union(int x, int y){
            int x_root = find(x);
            int y_root = find(y);
            if(x_root == y_root)return;
            parent[x_root] = y_root;
            count--;
        }

        public int find(int x){
            if(x == parent[x])return x;
            //路径压缩
            while(x != parent[x]){
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }
    }

MST17.07. 婴儿名字

public String[] trulyMostPopular(String[] names, String[] synonyms) {
        UnionFind f = new UnionFind();
        for(String s : names){
            int index = s.indexOf("(");
            String name = s.substring(0, index);
            int count = Integer.parseInt(s.substring(index + 1, s.length() - 1));
            //初始化
            f.parent.put(name, name);
            f.num.put(name, count);
        }

        for(String s: synonyms){
            int index = s.indexOf(",");
            String name1 = s.substring(1, index);
            String name2 = s.substring(index + 1, s.length() - 1);
            if(!f.parent.containsKey(name1)){
                f.parent.put(name1, name1);
                f.num.put(name1, 0);
            }
            if(!f.parent.containsKey(name2)){
                f.parent.put(name2, name2);
                f.num.put(name2, 0);
            }
            //合并
            f.union(name1, name2);
        }

        List<String> res = new ArrayList<>();
        for(String key : names){
            int index = key.indexOf("(");
            String name = key.substring(0, index);
            //这里要提前判断,如果在res里判断是否contains,则会超时
            //因为find了一遍,在res里又需要找一遍,耗时
            //只要当前name不是root则continue
            if(!f.find(name).equals(name))continue;
            res.add(name + "(" + f.num.get(name) + ")");
        }

        return res.toArray(new String[res.size()]);
    }

    class UnionFind{
        HashMap<String, String> parent;
        HashMap<String, Integer> num;

        public UnionFind(){
            parent = new HashMap<>();
            num = new HashMap<>();
        }

        public void union(String x, String y){
            String x_root = find(x);
            String y_root = find(y);
            if(x_root.equals(y_root))return;

            if(x_root.compareTo(y_root) < 0){
                parent.put(y_root, x_root);
                num.put(x_root, num.get(x_root) + num.get(y_root));
            }else{
                parent.put(x_root, y_root);
                num.put(y_root, num.get(x_root) + num.get(y_root));
            }
        }

        public String find(String x){
            // if(x.equals(parent.get(x)))return x;
            // //路径压缩
            // parent.put(x, find(parent.get(x)));
            // return parent.get(x);
            //测试效率是一样的
            return x.equals(parent.get(x)) ? x : find(parent.get(x));
        }
    }

总结

在刷了几道题之后,并查集的套路就是熟悉了,建立数据结构,union和find,先初始化,根据题目给的条件进行union,最后如果有要输出结果集的则find的根进行相应的统计即可
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值