力扣网---并查集---一月每日打卡

本文介绍了并查集在解决图的冗余连接、保证图可完全遍历、账户合并和最小费用连接所有点等问题中的应用。通过并查集算法,可以有效地检测环路、合并连通分量以及优化网络结构。示例代码展示了如何使用并查集解决这些实际问题。
摘要由CSDN通过智能技术生成

并查集介绍

看这篇介绍了解并查集:
酱懵静------并查集

冗余连接

/**
     * 在本问题中, 树指的是一个连通且无环的无向图。
     *
     * 输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
     *
     * 结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
     *
     * 返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
     *
     * 示例 1:
     *
     * 输入: [[1,2], [1,3], [2,3]]
     * 输出: [2,3]
     * 解释: 给定的无向图为:
     *   1
     *  / \
     * 2 - 3
     * 示例 2:
     *
     * 输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
     * 输出: [1,4]
     * 解释: 给定的无向图为:
     * 5 - 1 - 2
     *     |   |
     *     4 - 3
     *
     */


    public static void main(String[] args) {
        int[][] edges={{1,2}, {1,3}, {2,3}};
        System.out.println(Arrays.toString(findRedundantConnection(edges)));

        int[][] edges2={{1,2}, {2,3}, {3,4}, {1,4}, {1,5}};
        System.out.println(Arrays.toString(findRedundantConnection(edges2)));

        int[][] edges3={{1,2},{2,3},{1,5},{3,4},{1,4}};
        System.out.println(Arrays.toString(findRedundantConnection(edges3)));

        int[][] edges4={{4,5},{1,2},{2,4},{3,4},{2,3}};
        System.out.println(Arrays.toString(findRedundantConnection(edges4)));

    }

    //这题考察并查集  可以通过并查集寻找附加的边。
    //初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。
    //   如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。
    //   如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为附加的边,将当前的边作为答案返回。
    public static int[] findRedundantConnection(int[][] edges) {
        int nodesCount=edges.length;
        int[] parent=new int[nodesCount+1];
        for (int i = 0; i <nodesCount ; i++) {
            parent[i]=i;
        }
        for (int i = 0; i <nodesCount ; i++) {
            int[] edge=edges[i];
            int node1=edge[0],node2=edge[1];
            if(find(parent,node1)!=find(parent,node2)){
                union(parent,node1,node2);
            }else {
                return edge;
            }
        }

        return new int[0];
    }

    //合并并查集
    public static void union(int[] parent,int index1,int index2){
        parent[find(parent,index1)]=find(parent,index2);
    }

    //查找源头
    public static int find(int[] parent,int index){
        if(parent[index]!=index){
            parent[index]=find(parent,parent[index]);
        }
        return parent[index];
    }

保证图可完全遍历

 public class 保证图可完全遍历 {


    /**
     * Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3  种类型的边:
     *
     * 类型 1:只能由 Alice 遍历。
     * 类型 2:只能由 Bob 遍历。
     * 类型 3:Alice 和 Bob 都可以遍历。
     * 给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。
     * 请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。
     * 如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。
     *
     * 返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。
     *
     */

    public static void main(String[] args) {
        保证图可完全遍历 t=new 保证图可完全遍历();

        int[][] edges={{3,1,2},{3,2,3},{1,1,3},{1,2,4},{1,1,2},{2,3,4}};
        System.out.println(t.maxNumEdgesToRemove(4,edges));

    }

    public  int maxNumEdgesToRemove(int n, int[][] edges) {
        int res=0;
        UnionFind ufa=new UnionFind(n+1);
        UnionFind ufb=new UnionFind(n+1);
        //公共边
        for (int[] edge :edges){
            if(edge[0]==3){
                boolean u1=ufa.union(edge[1],edge[2]);
                boolean u2=ufb.union(edge[1],edge[2]);
                if(!u1&&!u2){
                    res++; //删除该公共边
                }
            }
        }

        for (int[] edge :edges){
           if(edge[0]==1){
               //只能由 Alice 遍历
               boolean u1=ufa.union(edge[1],edge[2]);
               if(!u1) res++;
           }else if (edge[0]==2){
               //只能由 Bob 遍历
               boolean u2=ufb.union(edge[1],edge[2]);
               if(!u2) res++;
           }
        }
        if(ufa.count!=1||ufb.count!=1){
            return -1;
        }
        return res;
    }


    private class UnionFind {

        private int[] parent;

        private int count;

        public int getCount() {
            return count;
        }

        public UnionFind(int n) {
            this.count = n-1;
            this.parent = new int[n];
            for (int i = 1; i < n; i++) {
                parent[i] = i;
            }
        }

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

        public boolean union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX == rootY) { //形成环
                return false;
            }

            parent[rootX] = rootY;
            count--;
            return true;
        }
    }

}

账户合并

public class 账户合并 {

    /**
     * 给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,
     * 其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。
     *
     * 现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,
     * 它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。
     *
     * 合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。账户本身可以以任意顺序返回。
     *
     * 示例 1:
     *
     * 输入:
     * accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"],
     * ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]]
     * 输出:
     * [["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'],
     * ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]]
     *
     * 解释:
     * 第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 "johnsmith@mail.com"。
     * 第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。
     * 可以以任何顺序返回这些列表,例如答案 [['Mary','mary@mail.com'],['John','johnnybravo@mail.com'],
     * ['John','john00@mail.com','john_newyork@mail.com','johnsmith@mail.com']] 也是正确的。
     *  
     *
     * 提示:
     * accounts的长度将在[1,1000]的范围内。
     * accounts[i]的长度将在[1,10]的范围内。
     * accounts[i][j]的长度将在[1,30]的范围内。
     *
     */


    public static void main(String[] args) {

        账户合并 h=new  账户合并();

        List<List<String>> accounts=new ArrayList<>();

        List<String> list1=Arrays.asList(new String[]{"John", "johnsmith@mail.com", "john00@mail.com"});
        List<String> list2=Arrays.asList(new String[]{"John", "johnnybravo@mail.com"});
        List<String> list3=Arrays.asList(new String[]{"John", "johnsmith@mail.com", "john_newyork@mail.com"});
        List<String> list4=Arrays.asList(new String[]{"Mary", "mary@mail.com"});
        accounts.add(list1);
        accounts.add(list2);
        accounts.add(list3);
        accounts.add(list4);

        System.out.println(h.accountsMerge(accounts));
    }


    public  List<List<String>> accountsMerge(List<List<String>> accounts) {
        Map<String,Integer> email2Index=new HashMap<>();
        Map<String,String> email2name=new HashMap<>();
        int emailsCount = 0;
        for (int i = 0; i < accounts.size(); i++) {
            List<String> account=accounts.get(i);
            String name=account.get(0);
            for (int j = 1; j < account.size() ; j++) {
                email2Index.put(account.get(j),emailsCount++);
                email2name.put(account.get(j),name);
            }
        }

        //并查集 合并index
        UnionFind uf=new UnionFind(emailsCount);
        for (List<String> account : accounts) {
            String firstEmail = account.get(1);
            int firstIndex=email2Index.get(firstEmail);
            for (int i =2; i < account.size(); i++) {
                int index2=email2Index.get(account.get(i));
                uf.union(firstIndex,index2);
            }
        }

        Map<Integer, List<String>> indexToEmails = new HashMap<Integer, List<String>>();
        for (String email:email2Index.keySet()) {
            int index=uf.find(email2Index.get(email));
            List<String> account=indexToEmails.getOrDefault(index,new ArrayList<String>());
            account.add(email);
            indexToEmails.put(index,account);
        }


        //生成结果
        List<List<String>> res=new ArrayList<>();
        for (List<String>  emails: indexToEmails.values()){
            Collections.sort(emails);
            String name=email2name.get(emails.get(0));
            List<String> account=new ArrayList<>();
            account.add(name);
            account.addAll(emails);
            res.add(account);
        }

        return res;
    }




    //并查集模板类
    class UnionFind {
         int[] parent;

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

        public void union(int index1, int index2) {
            parent[find(index2)] = find(index1);
        }

        public int find(int index) {
            if (parent[index] != index) {
                parent[index] = find(parent[index]);
            }
            return parent[index];
        }
    }


}

连接所有点的最小费用

public class 连接所有点的最小费用 {


    /**
     * 给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。
     *
     * 连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。
     *
     * 请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。
     *
     * 1 <= points.length <= 1000
     * -106 <= xi, yi <= 106
     * 所有点 (xi, yi) 两两不同。
     * @param args
     */

    public static void main(String[] args) {

        连接所有点的最小费用 t=new 连接所有点的最小费用();
        int[][] points= {{3,12},{-2,5},{-4,1}};
        System.out.println(t.minCostConnectPoints(points)); //18

        int[][] points2= {{0,0},{1,1},{1,0},{-1,1}};
        System.out.println(t.minCostConnectPoints(points2));


    }

    public  int minCostConnectPoints(int[][] points) {
        //先计算所有最短链接
        List<List<Integer>>  allConnectPoint=new ArrayList<>();
        for (int i = 0; i <points.length ; i++) {
            int[] p1=points[i];
            for (int j = i+1; j <points.length ; j++) {
                int[] p2=points[j];
                //计算距离
                int distance=Math.abs(p1[0]-p2[0])+Math.abs(p1[1]-p2[1]);
                List<Integer> list=Arrays.asList(new Integer[]{distance,i,j});
                allConnectPoint.add(list);
            }
        }
        Collections.sort(allConnectPoint,(a,b)->a.get(0)-b.get(0));
         int res=0;
         int n=0;
         UnionFind uf=new UnionFind(1001);
         for (List<Integer> list:allConnectPoint){
              int distance=list.get(0);
              int x=list.get(1),y=list.get(2);
              if(uf.union(x,y)){
                  res+=distance;
                  n++;
                  if(n==points.length-1)  break;
              }
         }

        return res;
    }

    //并查集模板类
    class UnionFind {
        int[] parent;

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

        public boolean union(int index1, int index2) {
            int x= find(index1),y=find(index2);
            if(x== y) return false;
            parent[find(index2)] = find(index1);
            return true;
        }

        public int find(int index) {
            if (parent[index] != index) {
                parent[index] = find(parent[index]);
            }
            return parent[index];
        }
    }

找到最小生成树里的关键边和伪关键边

public class 找到最小生成树里的关键边和伪关键边 {


    public static void main(String[] args) {

        找到最小生成树里的关键边和伪关键边 t = new 找到最小生成树里的关键边和伪关键边();
        int[][] edges = {{0, 1, 1}, {1, 2, 1}, {2, 3, 2}, {0, 3, 2}, {0, 4, 3}, {3, 4, 3}, {1, 4, 6}};
        System.out.println(t.findCriticalAndPseudoCriticalEdges(5, edges)); //[[0, 1], [2, 3, 4, 5]]

        int[][] edges2 ={{0,1,1},{1,2,1},{2,3,1},{0,3,1}};
        System.out.println(t.findCriticalAndPseudoCriticalEdges(5, edges2)); //[[], [0, 1, 2, 3]]


        int[][] edges3 ={{0,1,1},{0,3,1},{0,2,1},{1,2,1},{1,3,1},{2,3,1}};
        System.out.println(t.findCriticalAndPseudoCriticalEdges(4, edges3)); //[[], [0, 1, 2, 3, 4, 5]]

        int[][] edges4={{0,1,2},{0,2,5},{2,3,5},{1,4,4},{2,5,5},{4,5,2}};
        System.out.println(t.findCriticalAndPseudoCriticalEdges(4, edges4)); //[[0,2,3,5],[1,4]]
    }


    /**
     * 先排序
     * 使用克鲁斯卡尔算法 来构造最小生成数
     * 先计算一次 保存最小权重
     * 遍历  每次先确定一条边,然后计算每次获得的最小生成数的权重是否和最小权重相同。
     * 不相同这表示先确定的这条边是关键边。
     * 伪关键边:我们可以在计算最小生成树的过程中,最先考虑这条边,即最先将这条边的两个端点在并查集中合并。
     * 设最终得到的最小生成树权值为 vv,如果 v = \textit{value}v=value,那么这条边就是伪关键边。
     */

    class Edge{
        int oldIIndex;
        int wight;

        public Edge(int oldIIndex, int wight) {
            this.oldIIndex = oldIIndex;
            this.wight = wight;
        }
    }

    public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) {

        List<List<Integer>> res = new ArrayList<>();
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();

        //按权重排序
        List<Edge> edgeList=new ArrayList<>();
        for (int i = 0; i < edges.length; i++) {
            Edge edge=new Edge(i,edges[i][2]);
            edgeList.add(edge);
        }
        Collections.sort(edgeList,(a, b) -> a.wight - b.wight);
        Map<Integer,Integer> oldIndexMap =new HashMap<>();
        for (int i = 0; i < edgeList.size(); i++) {
            oldIndexMap.put(i,edgeList.get(i).oldIIndex);
        }
        Arrays.sort(edges, (a, b) -> a[2] - b[2]);



        //先计算一次最小权值
        int mixTreeWeight = getMixTreeWeight( edges, -1,false);
        for (int i = 0; i < edges.length; i++) {
            int nowWight = getMixTreeWeight( edges, i,false);
            if (nowWight == mixTreeWeight) {
                nowWight=getMixTreeWeight(edges,i,true);
                if(nowWight==mixTreeWeight) list2.add(oldIndexMap.get(i));
            }else {
                list1.add(oldIndexMap.get(i));
            }
        }

        Collections.sort(list1);
        Collections.sort(list2);
        res.add(list1);
        res.add(list2);
        return res;
    }


    public int getMixTreeWeight( int[][] edges, int index,Boolean fristAdd) {
        int wight = 0;
        UnionFind uf = new UnionFind(200);
        if(fristAdd) {
            uf.union(edges[index][0],edges[index][1]); //先加这条边
            wight += edges[index][2];
        }
        for (int i = 0; i < edges.length; i++) {
            if (i != index) { //排除一条边
                int x = edges[i][0], y = edges[i][1];
                if (uf.find(x) != uf.find(y)) {
                    uf.union(x, y);
                    wight += edges[i][2];
                }
            }
        }
        return wight;
    }


    //并查集模板类
    class UnionFind {
        int[] parent;

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

        public void union(int index1, int index2) {
            parent[find(index2)] = find(index1);
        }

        public int find(int index) {
            if (parent[index] != index) {
                parent[index] = find(parent[index]);
            }
            return parent[index];
        }
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值