图论之并查集模板和经典例题分析(Java语言描述)
Posted nuist__NJUPT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论之并查集模板和经典例题分析(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] ;
以上是关于图论之并查集模板和经典例题分析(Java语言描述)的主要内容,如果未能解决你的问题,请参考以下文章
带权并查集(含种类并查集)经典模板 例题:①POJ 1182 食物链(经典)②HDU - 1829 A bug's life(简单) ③hihoCoder 1515 : 分数调查(示例代码(代