题目
资源限制
内存限制:512.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s
问题描述
一开始有n个集合,{1},{2}……{n}。
定义一个操作,给定两个数a,b,用a,b所在的集合的并集代替a和b原来所在的集合。
求操作之后的集合个数。
输入格式
第一行是两个自然数n,m(其中m小于n,m和n小于3000),分别表示一开始的集合个数和操作的总数。
接下来m行,每行两个整数a,b,表示合并a,b所在集合。
输出格式
一个整数,即最后的集合个数。
样例输入
6 4
1 2
2 3
3 6
4 5
样例输出
2
思路:并查集
并查集的核心就两个点:union和find。
唯一特殊的就是当查的时候,将孩子全记录下来。当查到父亲的时候,直接将父亲和孩子直接相连。
这个算法很简单,只需要一个parent[]数组、union()函数、find()函数即可。Prim算法中,来辨别两个点是否相连过。这是HashMap之类直接存储连接关系不能比的,因为其中连起来一条边的时候,相对性的集合也会相连,这正是并查集所擅长的。
import java.util.HashSet;
import java.util.Scanner;
import java.util.Stack;
public class test
{
static int[] par;
public static void main(String[] args)
{
Scanner in =new Scanner(System.in);
int N=in.nextInt();int M=in.nextInt();
par=new int[N+1];
for(int i=0;i<N+1;i++) //初始化par数组
par[i]=i;
for(int i=0;i<M;i++) //开始合并
union(in.nextInt(), in.nextInt());
HashSet<Integer> hs=new HashSet<Integer>();
for(int i=1;i<N+1;i++) //查询并记录父亲,一个父亲只记录一次
hs.add(find(i));
System.out.println(hs.size());
}
static void union(int a ,int b)
{
int par_a=find(a);
int par_b=find(b);
if(par_a!=par_b) //如果是同一个父亲不用任何操作
par[par_b]=par_a; //是不同父亲,那么随便将一个的父亲改为另一个的
}
static int find(int a)
{
int j=par[a];
if(par[j]==j) //如果他的父亲是根节点,那么直接返回
return j;
int t=a;
Stack<Integer> s=new Stack<Integer>();
while(par[t]!=t)
{
s.add(t); //使用Stack存放中间遍历的节点,没用递归是放置数据特别大的时候栈溢出,Java的特别容易溢出
t=par[t];
}
while(!s.empty()) //优化,让所有的孩子直接指向父亲
{
j=s.pop();
par[j]=t;
}
return t;
}
}