并查集:Union Find :一种不一样的树形结构:由孩子指向父亲
用处:网络中节点间的连接状态,路径问题,朋友问题。
对于一组数据,主要支持两个动作:
union(p,q)
isConnected(p,q)
上面代表的是数字,下面代表的是 id值,这样可以明显看到,0到4属于同一个集合,5到9属于第二个集合。因为各自的id值相等
第一个并查集的实现方法:
定义一个接口
public interface UF {
int getSize();
boolean isConnected(int p,int q);
void unionElements(int p,int q);
}
import java.util.Scanner;
public class UnionFind implements UF {
private int [] id ;//初始定义的数组
public UnionFind(int size){
id = new int[size];
for (int i=0;i<id.length;i++){
id[i]=i;//把每个id相当于集合的索引设置初始的值
}
}
@Override
public int getSize() {
return id.length;
}
//查找元素p所对应的集合编号
public int find(int p){
if (p <0 && p>=id.length)
throw new IllegalArgumentException("p is out of bound");
return id[p];
}
//用于查看元素p和元素q是否属于同一个集合
@Override
public boolean isConnected(int p, int q) {
return find(p)==find(q);
}
//合并元素
@Override
public void unionElements(int p, int q) {
int pid = find(p);
int qid = find(q);
if (pid==qid){
return;
}
for (int i = 0;i < id.length;i++){
if (id[i] == pid){
id[i] = qid;
}
}
}
}
class Main{
public static void main(String[] args){
Scanner sr = new Scanner(System.in);
UnionFind u = new UnionFind(4);
int a = sr.nextInt();
int b = sr.nextInt();
// int c = sr.nextInt();
u.unionElements(a,b);
System.out.println(u.find(a));
System.out.println(u.find(b));
}
}
第二个方法用树来实现:
每一个节点都指向自己都是一棵树 :注意下面的值的变化,每次联合其实相当于去联合根节点
查询这是就是log(h)h代表的深度,虽然牺牲了查询时间但是节省了合并时间
public interface UF {
int getSize();
boolean isConnected(int p, int q);
void unionElements(int p, int q);
}
public class UnionFind2 implements UF{
private int [] parent;//表示第i个元素指向那个节点
public UnionFind2(int size){
parent = new int[size];
for (int i = 0; i <size; i++) {
parent[i]=i;//初始化时候每一个几点都指向他自己
}
}
@Override
public int getSize() {
return parent.length;
}
/**
* 查找过程,查找元素p所对应的集合编号
* O(h)的复杂度,h为树的深度
* @param p
* @return
*/
private int find(int p){
while(p!=parent[p]){
p=parent[p];//不断去找根节点,知道找到对应的根节点
}
return p;
}
//查看元素p和元素q是否属于一个集合
@Override
public boolean isConnected(int p, int q) {
return false;
}
//合并元素p和元素q所属的集合
//O(h)的复杂度,h为树的深度
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot==qRoot){
return;
}
parent[pRoot]=qRoot;//指向根节点即可
}
}
加入了节点的判断 这样非常省时间:
public interface UF {
int getSize();
boolean isConnected(int p, int q);
void unionElements(int p, int q);
}
public class Main implements UF {
private int [] parent;//表示第i个元素指向那个节点
private int [] sz;//表示以sz[i]以i为根的集合中元素的个数
public Main(int size){
parent = new int[size];
sz = new int [size];
for (int i = 0; i <size; i++) {
parent[i]=i;//初始化时候每一个几点都指向他自己
sz[i]=1;//每一个元素初始时候自己都是一个独立的集合
}
}
@Override
public int getSize() {
return parent.length;
}
/**
* 查找过程,查找元素p所对应的集合编号
* O(h)的复杂度,h为树的深度
* @param p
* @return
*/
private int find(int p){
while(p!=parent[p]){
p=parent[p];//不断去找根节点,知道找到对应的根节点
}
return p;
}
//查看元素p和元素q是否属于一个集合
@Override
public boolean isConnected(int p, int q) {
return false;
}
//合并元素p和元素q所属的集合
//O(h)的复杂度,h为树的深度
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot==qRoot){
return;
}
if (sz[pRoot]<sz[qRoot]){
parent[pRoot]=qRoot;//p个几点所在的个数相对于比较少,让元素个数比较少的指向元素个数比较多的根节点
sz[qRoot]+=sz[pRoot];
}else{
parent[qRoot]=pRoot;
sz[pRoot]+=sz[qRoot];
}
}
}