什么并查集
java代码
特别注意:并查集数组中,索引为元素,值为分组标志,比如城市1,2,3就是索引,值都是1的话,表示都在组1中。
在这里插入代码片package mypackage;
import java.util.Scanner;
//并查集
class UF{
//并查集数组,索引为元素,值为分组标志,比如城市1,2,3就是索引,值都是1的话,表示都在组1中
private int[] arr;
private int count;//分组个数,多少个组
//构造方法,传入分组的个数,默认每个元素单独一组
public UF(int N) {
this.count = N;
this.arr=new int[N];
//每个元素一组
for (int i = 0; i < arr.length; i++) {
arr[i]=i;
}
}
//获取分组的个数
public int getCount(){
return count;
}
//查找某个元素所在的组,注意元素就是索引,所在的组就是数组的值
public int find(int p){
return arr[p];
}
//判断是否在一个分组中,即查看是否这两个索引对应的值是否相等
public boolean connected(int p,int q){
return find(p)==find(q);
}
//合并分组,即使得两个元素处于同一组,即使得两个索引处的值相等
public void union(int p,int q){
// 如果已经在一组中,直接返回
if (connected(p,q)){
return;
}else {
// 获取p q所在的分组
int pGroup=find(p);
int qGroup=find(q);
// 注意这里是将和p是一组的所有的元素的组标识都变成q的组标识,而不是只变化一个,
// 因此要用循环,而不是只arr[p]=qGroup
for (int i = 0; i < arr.length; i++) {
if (arr[i]==pGroup){
arr[i]=qGroup;
}
}
// 减少分组个数
count--;
}
}
}
//测试
public class MyJava {
public static void main(String[] args) {
UF uf=new UF(5);
System.out.println("默认并查集中有:"+uf.getCount()+"个分组");
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请输入第一个要合并的元素:");
int p=sc.nextInt();
System.out.println("请输入第二个要合并的元素:");
int q=sc.nextInt();
if (uf.connected(p,q)){
System.out.println("这两个元素已经在一个分组中");
}else {
uf.union(p,q);
System.out.println("合并完毕,当前还有"+uf.getCount()+"个分组");
}
}
}
}
并查集改进1
之前在合并的时候,union使用还是使用了循环,如果数据量较大的话,需要循环将组的标志更改,性能不佳,为了改进这个性能,进行修改。
/*
* 改进的地方主要是find和union方法
* 现在并查集数组,索引为元素,值为元素分组的父节点,根据父节点找根节点,根节点才是分组标志
* 比如索引0对应的值为2,然后再找索引2的值为4,索引4的值为4,直到索引对应的值和索引相同,即停止,表示索引0对应的分组为4
*
* */
package mypackage;
import java.util.Scanner;
//并查集
class UF{
//并查集数组,索引为元素,值为元素分组的父节点,根据父节点找根节点,根节点才是分组标志
// 比如索引0对应的值为2,然后再找索引2的值为4,索引4的值为4,直到索引对应的值和索引相同,即停止,表示索引0对应的分组为4
private int[] arr;
private int count;//分组个数,多少个组
//构造方法,传入分组的个数,默认每个元素单独一组
public UF(int N) {
this.count = N;
this.arr=new int[N];
//每个元素一组
for (int i = 0; i < arr.length; i++) {
arr[i]=i;
}
}
//获取分组的个数
public int getCount(){
return count;
}
//查找某个元素所在的组,注意元素就是索引,所在的组就是数组的值
/*改进前
public int find(int p){
return arr[p];
}*/
// 改进后
public int find(int p){
while (true){
if (p==arr[p]){
return p;
}else {
p=arr[p];
}
}
}
//判断是否在一个分组中,即查看是否这两个索引对应的值是否相等
public boolean connected(int p,int q){
return find(p)==find(q);
}
//合并分组,即使得两个元素处于同一组,即使得两个索引处的值相等
/*改进前
public void union(int p,int q){
// 如果已经在一组中,直接返回
if (connected(p,q)){
return;
}else {
// 获取p q所在的分组
int pGroup=find(p);
int qGroup=find(q);
// 注意这里是将和p是一组的所有的元素的组标识都变成q的组标识,而不是只变化一个,
// 因此要用循环,而不是只arr[p]=qGroup
for (int i = 0; i < arr.length; i++) {
if (arr[i]==pGroup){
arr[i]=qGroup;
}
}
// 减少分组个数
count--;
}
}*/
// 改进后
public void union(int p,int q){
int pGroup=find(p);
int qGroup=find(q);
if (pGroup==qGroup){
return;
}else {
// 直接让根节点的值为qGroup,因为根节点才是组的标识
arr[pGroup]=qGroup;
count--;
}
}
}
//测试
public class MyJava {
public static void main(String[] args) {
UF uf=new UF(5);
System.out.println("默认并查集中有:"+uf.getCount()+"个分组");
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请输入第一个要合并的元素:");
int p=sc.nextInt();
System.out.println("请输入第二个要合并的元素:");
int q=sc.nextInt();
if (uf.connected(p,q)){
System.out.println("这两个元素已经在一个分组中");
}else {
uf.union(p,q);
System.out.println("合并完毕,当前还有"+uf.getCount()+"个分组");
}
}
}
}
并查集改进2
以上虽然我们改变了union方法,使得不用循环,但是find方法内却使用循环,如果链很长,find同样效率不高,导致链很长的原因之一可能是在合并存在问题,上面合并时,我们是暴力的合并,如果将大树的根节点指向小树的根节点的合并方式,则会增加数的深度,反之如果将小树合并到大树上,则深度不会增加,因此我们可以将组内元素较小的组合并到较大组中,这样就会降低find的查找深度。
/*
* 改进的地方主要是union方法
* 如果让较小的组合并到较大的组上,树的深度就可能降低,增加效率
* */
package mypackage;
import java.util.Scanner;
//并查集
class UF{
//并查集数组,索引为元素,值为元素分组的父节点,根据父节点找根节点,根节点才是分组标志
// 比如索引0对应的值为2,然后再找索引2的值为4,索引4的值为4,直到索引对应的值和索引相同,即停止,表示索引0对应的分组为4
private int[] arr;
private int count;//分组个数,多少个组
private int[] sz;//记录元素对应的分组中元素的个数
//构造方法,传入分组的个数,默认每个元素单独一组
public UF(int N) {
this.count = N;
this.arr=new int[N];
//每个元素一组
for (int i = 0; i < arr.length; i++) {
arr[i]=i;
}
this.sz=new int[N];
//每个元素一组,初始情况下,每个组一个元素
for (int i = 0; i < arr.length; i++) {
sz[i]=1;
}
}
//获取分组的个数
public int getCount(){
return count;
}
//查找某个元素所在的组,注意元素就是索引,所在的组就是数组的值
/*改进前
public int find(int p){
return arr[p];
}*/
// 改进后
public int find(int p){
while (true){
if (p==arr[p]){
return p;
}else {
p=arr[p];
}
}
}
//判断是否在一个分组中,即查看是否这两个索引对应的值是否相等
public boolean connected(int p,int q){
return find(p)==find(q);
}
//合并分组,即使得两个元素处于同一组,即使得两个索引处的值相等
/*改进前
public void union(int p,int q){
// 如果已经在一组中,直接返回
if (connected(p,q)){
return;
}else {
// 获取p q所在的分组
int pGroup=find(p);
int qGroup=find(q);
// 注意这里是将和p是一组的所有的元素的组标识都变成q的组标识,而不是只变化一个,
// 因此要用循环,而不是只arr[p]=qGroup
for (int i = 0; i < arr.length; i++) {
if (arr[i]==pGroup){
arr[i]=qGroup;
}
}
// 减少分组个数
count--;
}
}*/
/*
// 第一次改进后
public void union(int p,int q){
int pGroup=find(p);
int qGroup=find(q);
if (pGroup==qGroup){
return;
}else {
// 直接让根节点的值为qGroup,因为根节点才是组的标识
arr[pGroup]=qGroup;
count--;
}
}*/
// 第二次改进
// 比较组内元素的个数,每次都将较小的组合并到较大的组上,且更改组内元素个数
public void union(int p,int q){
int pGroup=find(p);
int qGroup=find(q);
if (pGroup==qGroup){
return;
}else {
// pGroup组内元素个数较小
if (sz[pGroup]<sz[qGroup]){
arr[pGroup]=qGroup;
sz[qGroup]=sz[qGroup]+sz[pGroup];
}else {
// qGroup组内元素个数较小
arr[qGroup]=pGroup;
sz[pGroup]=sz[pGroup]+sz[qGroup];
}
count--;
}
}
}
//测试
public class MyJava {
public static void main(String[] args) {
UF uf=new UF(5);
System.out.println("默认并查集中有:"+uf.getCount()+"个分组");
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请输入第一个要合并的元素:");
int p=sc.nextInt();
System.out.println("请输入第二个要合并的元素:");
int q=sc.nextInt();
if (uf.connected(p,q)){
System.out.println("这两个元素已经在一个分组中");
}else {
uf.union(p,q);
System.out.println("合并完毕,当前还有"+uf.getCount()+"个分组");
}
}
}
}