图论之并查集模板和经典例题分析(Java语言描述)

图论之并查集模板和经典例题分析

  • 并查集:
  • 在一些有N个元素的集合应应用中,我们通常在开始时候让每个元素构成一个单元素的集合,然后按照一定的顺序将属于同一组的元素所在集合合并,期间要反复查找每个元素的所在集合。
  • 主要方法:
  • find:查询某个数据属于哪个分组
  • union:合并两个分组到同一个集合

1-基础模板


public class Main {
    static int [] parent ;
    public Main(int N){
        parent = new int [N] ;
        for(int i=0; i<N; i++){
            parent[i] = i ;
        }
    }
    public int find(int x){
        if(parent[x] != x){
            parent[x] = find(parent[x]) ;
        }
        return parent[x] ;
    }

    public void union(int x, int y){
        parent[find(x)] = find(y) ;
    }
    
}

2-优化模板

import java.util.Arrays;

public class Main2 {
    static int [] parent ;
    static int [] rank ;
    public Main2(int N){
        parent = new int [N] ;
        rank = new int [N] ;
        for(int i=0; i<N; i++){
            parent[i] = i ;
        }
        Arrays.fill(rank, 1) ;
    }
    public int find(int x){
        if(x != parent[x]){
            parent[x] = find(parent[x]) ;
        }
        return parent[x] ;
    }
    public void union(int x, int y){
        int rootX = find(x) ;
        int rootY = find(y) ;
        if(rootX == rootY){
            return ;
        }
        if(rank[rootX] > rank[rootY]){
            parent[rootY] = rootX ;
        }else if(rank[rootX] < rank[rootY]){
            parent[rootX] = rootY ;
        }else{
            parent[rootX] = rootY ;
            rank[rootX] ++ ;
        }
    }
}

3-经典例题

  • 1-亲戚问题
  • 或许你并不知道,你的某个朋友是你的亲戚。他可能是你的曾祖父的外公的女婿的外甥女的表姐的孙子。如果能得到完整的家谱,
  • 判断两个人是否是亲戚应该是可行的,但如果两个人的最近公共祖先与他们相隔好几代,使得家谱十分庞大,
  • 那么检验亲戚关系实非人力所能及。在这种情况下,最好的帮手就是计算机。为了将问题简化,你将得到一些亲戚关系的信息,
  • 如Marry和Tom是亲戚,Tom和Ben是亲戚,等等。从这些信息中,你可以推出Marry和Ben是亲戚。
  • 请写一个程序,对于我们的关于亲戚关系的提问,以最快的速度给出答案。
  • 输入
  • 输入由两部分组成。
  • 第一部分以N,M开始。N为问题涉及的人的个数(1≤N≤20000)。这些人的编号为1,2,3,…, N。下面有M行(1≤M≤1000000), 每行有两个数ai,bi,表示已知ai 和bi是亲戚。
  • 第二部分以Q开始。以下Q行有Q个询问(1≤ Q ≤1000000),每行为ci,di,表示询问ci 和di是否为亲戚。
  • 输出
  • 对于每个询问ci,di,输出一行:若ci 和di为亲戚,则输出“Yes”,否则输出“No”。
  • 样例输入
  • 10 7
  • 2 4
  • 5 7
  • 1 3
  • 8 9
  • 1 2
  • 5 6
  • 2 3
  • 3
  • 3 4
  • 7 10
  • 8 9
  • 样例输出
  • Yes
  • No
  • Yes

import java.util.Scanner;
public class Main3 {
    static int N ;
    static int M ;
    static int Q ;
    static int [] parent ;
    static int [] rank  ;
    static String [] res ;

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in) ;
        N = input.nextInt() ;
        M = input.nextInt() ;

        init(N)  ;
        for(int i=0; i<M; i++){
            int a = input.nextInt() ;
            int b = input.nextInt() ;
            merge(a, b) ;
        }
        Q = input.nextInt() ;
        res = new String[Q] ;
        for(int i=0; i<Q; i++){
            int a = input.nextInt() ;
            int b = input.nextInt() ;

           // res[i] = find(a) == find(b) ? "Yes" : "No" ;
            if(find(a) == find(b)){
                res[i] = "Yes" ;
            }else{
                res[i] = "No" ;
            }
        }
        for(int i=0; i<res.length; i++){
            System.out.println(res[i]);
        }
    }

    private static void init(int N) { //初始化集合元素和等级
        parent = new int [N+1] ;
        rank = new int [N+1] ;
        for(int i=1; i<=N; i++){
            parent[i] = i ;
            rank[i] = 1 ;
        }
    }

    private static void merge(int a, int b) { //两个集合合并成一个集合
        int x = find(a) ;
        int y = find(b) ;
        if(x == y){
            return ;
        }
        if(rank[x] > rank[y]){
            parent[y] = x ;
        }else if(rank[x] < rank[y]){
            parent[x] = y ;
        }else{
            parent[x] = y ;
            rank[y] ++ ;
        }
    }

    private static int find(int x){ //判别是否属于同一个集合
        if(x != parent[x]){
            parent[x] = find(parent[x]) ;
        }
        return parent[x] ;
    }
}

2-数码世界

  • 题目描述
  • 有一个叫做“数码世界”奇异空间,在数码世界里生活着许许多多的数码宝贝,其中有些数码宝贝之间可能是好朋友,
  • 并且数码宝贝世界有两条不成文的规定:
  • 第一,数码宝贝A和数码宝贝B是好朋友等价于数码宝贝B与数码宝贝A是好朋友
  • 第二,如果数码宝贝A和数码宝贝C是好朋友,而数码宝贝B和数码宝贝C也是好朋友,那么A和B也是好朋友
  • 现在给出这些数码宝贝中所有好朋友的信息,问:可以把这些数码宝贝分成多少组,满足每组中的任意两个
  • 数码宝贝都是好朋友,而且任意两组之间的数码宝贝都不是好朋友
  • 输入格式
  • 输入的第一行有两个正整数n(n <= 100)和m(m <= 100),分别表示数码宝贝的个数和好朋友的组数,
  • 其中数码宝贝编号为1~n
  • 接下来有m行,每行两个正整数a和b,表示数码宝贝a和数码宝贝b是好朋友
  • 输出格式
  • 输出一个整数,表示这些数码宝贝可以分成的组数
  • 样例输入
  • 7 5
  • 1 2
  • 2 3
  • 3 1
  • 1 4
  • 5 6
  • 样例输出
  • 3


import java.util.Scanner;

public class Main4 {
    static int n ;
    static int m ;
    static int [] fa ;
    static int [] rank ;
    static int count = 0 ;
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in) ;
        n = input.nextInt() ; //数码宝贝个数
        m = input.nextInt() ; //好朋友组数
        init(n) ;
        for(int i=0; i<m; i++){
            int x = input.nextInt() ;
            int y  = input.nextInt() ;
            merge(x, y) ;
        }
        for(int  i=1; i<=n; i++){ //集合根节点的数目就是集合个数,即组数
            if(find(i) == i){
                count ++ ;
            }
        }
        System.out.println(count);
    }

    private static void init(int n) {
        fa = new int [n+1] ;
        rank = new int [n+1] ;
        for(int i=1; i<=n; i++){
            fa[i] = i ;
            rank[i] = 1 ;
        }
    }

    private static void merge(int x, int y){
        int rootX = find(x) ;
        int rootY = find(y) ;
        if(rootX == rootY){
            return ;
        }
        if(rank[rootX] > rank[rootY]){
            fa[rootY] = rootX ;
        }else if(rank[rootX] < rank[rootY]){
            fa[rootX] = rootY ;
        }else{
            fa[rootX] = rootY ;
            rank[rootY]++ ;
        }
    }

    private static int find(int x) {
        if(x != fa[x]){
            fa[x] = find(fa[x]) ;
        }
        return fa[x] ;
    }
}

3-畅通工程

  • 1.某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。
  • 省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。
  • 问最少还需要建设多少条道路?
  • Input
  • 测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;
  • 随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
  • 注意:两个城市之间可以有多条道路相通,也就是说
  • 3 3
  • 1 2
  • 1 2
  • 2 1
  • 这种输入也是合法的
  • 当N为0时,输入结束,该用例不被处理。
  • Sample Input
  • 4 2
  • 1 3
  • 4 3
  • 3 3
  • 1 2
  • 1 3
  • 2 3
  • 5 2
  • 1 2
  • 3 5
  • 999 0
  • 0
  • Sample Output
  • 1
  • 0
  • 2
  • 998

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

public class Main5 {
    static int N = 1  ;
    static int M ;
    static int [] fa ;
    static int [] rank ;
    static List<Integer> list = new ArrayList<>() ;

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in) ;

        while(N != 0){
            N = input.nextInt() ;
            if(N == 0){
                break ;
            }
            M = input.nextInt() ;
            init(N) ;
            for(int i=0; i<M; i++){
                 int x = input.nextInt() ;
                 int y = input.nextInt() ;
                 merge(x, y) ;
            }
            int cnt = 0 ;
            for(int i=1; i<=N; i++){
                if(i == find(i)){
                    cnt ++ ;
                }
            }
            list.add(cnt-1) ; //还需要建设道路数等于当前集合数减1
        }
        for(int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }
    }
    private static void init(int n) {
        fa = new int [n+1] ;
        rank = new int [n+1] ;
        for(int i=1; i<=n; i++){
            fa[i] = i ;
            rank[i] = 1 ;
        }
    }

    private static void merge(int x, int y){
        int rootX = find(x) ;
        int rootY = find(y) ;
        if(rootX == rootY){
            return ;
        }
        if(rank[rootX] > rank[rootY]){
            fa[rootY] = rootX ;
        }else if(rank[rootX] < rank[rootY]){
            fa[rootX] = rootY ;
        }else{
            fa[rootX] = rootY ;
            rank[rootY]++ ;
        }
    }

    private static int find(int x) {
        if(x != fa[x]){
            fa[x] = find(fa[x]) ;
        }
        return fa[x] ;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nuist__NJUPT

给个鼓励吧,谢谢你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值