并查集
作用:用来查找某个图中是否含有闭环。
比如图一:
上图中就是没有闭环的一个图,而下图(图二)就是一个有闭环的图
思路1-数组寻根法:
顾名思义,数组寻根法(自己称呼的)就是寻找每个节点的根节点,然后判断根节点是否相同。
具体的做法如下:
1.遍历每一条路径,然后根据路径去寻找根节点
2.如果有两个节点的根节点是相同的,那么就是可以组成闭环,如果到遍历完毕所有的路径之后还是没有相同根节点的两个点,说明这些点中不能够组成闭环
我们来用并查集的思想走一下图一
(1)首先创建拥有节点个数的整形数组arr,用来存放各个节点的根节点,在此我们分配4个单位空间就好了(0,1,2,3),并且给各个数值赋值为-1
arr[0]=-1;
arr[1]=-1;
arr[2]=-1;
arr[3]=-1;
(2)遍历到路径①:首先对根节点是否相同进行判断,节点0的根节点arr[0]:-1,因为根节点是初始赋值,所以根节点视为本身,所以arr[0]:0,同理节点1的根节点是arr[1]:1 两节点的根节点不同,合并一下 arr[0]=arr[1],所以节点0的根节点就发生了变化。
arr[0]=1;
arr[1]=1;
arr[2]=-1;
arr[3]=-1;
(2)遍历到路径②:首先对根节点是否相同进行判断,节点1的根节点arr[1]:1 节点2的根节点arr[2]:2(同上) 根节点不同 转存根节点arr[1]=arr[2] 到此节点1的根节点指向了节点2的根节点,并且所以以节点1为根节点的节点(在这里是节点0)都要转存根节点
arr[0]=2;
arr[1]=2;
arr[2]=2;
arr[3]=-1;
(3)遍历到路径③:首先对根节点是否相同进行判断,节点0的根节点arr[0]:2 节点3的根节点arr[3]:3(同上) 根节点不同 转存根节点arr[0]=arr[3] 到此节点0的根节点指向了节点3的根节点,并且所有和节点0根节点相同的节点(在这里是节点1,和节点2)都要转存根节点
arr[0]=3;
arr[1]=3;
arr[2]=3;
arr[3]=3;
到这里可能有人会问了,明明有四个节点是相同的,为什么不算是闭环,原因是,我们判断节点是否相同的时间点是在我们遍历路径的第一时间,如果两节点相同,那么就是能够组成闭环的,如果不相同,那么就是没有闭环,接下来我们依照图2继续遍历路径。
(4)遍历到路径④:首先对根节点是否相同进行判断,节点2的根节点arr[2]:3 节点3的根节点arr[3]:3 OK!OK!OK!停住停住,节点相同,有闭环。
arr[0]=3;
arr[1]=3;
arr[2]=3;
arr[3]=3;
至此,判断并查集的功能结束,我这种方法实际上需要耗费的空间还是蛮大的,但是相对于另一种网上的并查集查询的方法还是更容易理解的(鄙人个人认为),接下来出示另一种思路的分析过程:
(1)首先依旧是创建拥有节点个数的整形数组arr,用来存放各个节点的根节点,在此我们分配4个单位空间(0,1,2,3),并且给各个数值赋值为-1
arr[0]=-1;
arr[1]=-1;
arr[2]=-1;
arr[3]=-1;
(2)遍历到路径①:首先对根节点是否相同进行判断,节点0的根节点arr[0]:-1,因为根节点是初始赋值,所以根节点视为本身,所以arr[0]:0,同理节点1的根节点是arr[1]:1 两节点的根节点不同,合并一下 arr[0]=arr[1],所以节点0的根节点就发生了变化。
arr[0]=1;
arr[1]=1;
arr[2]=-1;
arr[3]=-1;
(2)遍历到路径②:首先对根节点是否相同进行判断,节点1的根节点arr[1]:1 节点2的根节点arr[2]:2(同上) 根节点不同 在转存根节点之前有其他的工作,就是寻找到真实根节点,因为节点0的根节点是1,所以节点0的根节点实际上是节点1的根节点:1 转存真实根节点arr[1]=arr[2] 到此,节点的变化如下:
arr[0]=1;
arr[1]=2;
arr[2]=2;
arr[3]=-1;
(3)遍历到路径③:首先对根节点是否相同进行判断,节点0的根节点arr[0]:1 节点3的根节点arr[3]:3(同上) 根节点不同 寻找到真实根节点,因为节点0的根节点是1,节点1的根节点是2所以节点0的真实根节点2:1转存根节点arr[2]=arr[3] 到此,节点的变化如下:
arr[0]=1;
arr[1]=2;
arr[2]=3;
arr[3]=3;
同样到这里了,我们依旧没有判断闭环
(4)遍历到路径④:首先对根节点是否相同进行判断,节点2的根节点arr[2]:3 节点3的根节点arr[3]:3 !!!停住停住,节点相同,有闭环。
arr[0]=1;
arr[1]=2;
arr[2]=3;
arr[3]=3;
代码实现
public class 并查集算法 {
public static void main(String[] args)
{
int[] arr= new int[4];
get(arr);//给arr赋值
int flage=0;
int[][] edgs= //表示两个点相连的路径集合
{
{0,1},{1,2},{2,3},{0,3}
};
for(int i=0;i<4;i++)//进行循环来判断是否含有闭环
{
System.out.println("*************这是在进行第"+i+"次循环*************");
int x=edgs[i][0];//x是这个集合每一个元素的第一个点
int y=edgs[i][1];//y是这个集合每一个元素的第二个点
//通过x和y的联立来判断是否含有闭环
if(union(x,y,arr)==0)//因为函数中约定如果返回0的话那么久表示有闭环
{
flage=1;
}
}
if(flage==0)
{
System.out.println("Haven't");
}else
{
System.out.println("Have");
}
}
public static int find_root(int x,int arr[])//寻找根节结点函数
{
int x_root=x;//定义x的根节点就是x,这是假设步骤,下面进行寻找
while(arr[x_root]!=-1)//如果这个假设的根节点的值不是一开始赋值的-1
{
System.out.println("当前值:"+x_root+"\tarr["+x+"]:"+arr[x_root]);
x_root=arr[x_root];//就开始寻找这个假设的根节点的真实的根节点
}
return x_root;//返回这个根节点的值
}
public static int union(int x,int y,int arr[])//合并结点函数
{
//如果返回1表示可以成功的将其合并,如果返回0的时候表示合并失败
int x_root = find_root(x,arr);//寻找x的根节点赋值给x_root
System.out.println("x的根节点是:"+x_root);
int y_root = find_root(y,arr);//寻找y的根节点赋值给y_root
System.out.println("y的根节点是:"+y_root);
if(x_root == y_root)//如果两个结点相同,证明可以将其合并,返回0
{
System.out.println("x和y的结点相同");
return 0;
}else //否则将y的根节点指向
{
System.out.println("x和y的根节点不同,所以把x的根节点指向了y,即将y_root:"
+y_root+"赋值给了arr["+x_root+"]");
arr[x_root]=y_root;
System.out.println("此时arr["+x_root+"]:"+arr[x_root]);
return 1;
}
}
public static void get(int arr[])//给arr赋值的一个函数
{
for(int i=0;i<arr.length;i++)
{
arr[i]=-1;
}
}
}
思路2-穿针引线法:
具体思路和我认为的数组寻根法(第一开始讲到的那种)类似,废话不多说,直接走:
(1)这次创建的事的字符串数组arr,用来存放各个节点的所能够到达的节点,在此我们分配4个单位空间(0,1,2,3),并且给各个数值赋值为索引数值(因为本身是可以到达本身节点的)。
arr[0]=0;
arr[1]=1;
arr[2]=2;
arr[3]=3;
(2)遍历到路径①:首先对节点能够到达的地方进行判断,看看节点0和节点1之间是否互通(检查方式就是arr[0]中是否含有字符“1”或者arr[1]中是否含有“0”),节点0能够到达的路径是arr[0]:0,节点1能够到达的路径是arr[1]:1,因为路径①是节点0和节点1之间的路径,所以可以理解为节点0和节点1可以互相到达,所以更新节点如下:
arr[0]=01;
arr[1]=01;
arr[2]=2;
arr[3]=3;
(2)遍历到路径②:依然是看看节点能否互相到达,路径②是节点1和节点2之间的连接,节点1可以到达的节点是0和1,不可以到达节点2,然后进行更新:
arr[0]=012;
arr[1]=012;
arr[2]=012;
arr[3]=3;
(3)遍历到路径③:依然是看看节点能否互相到达,路径③是节点0和节点3之间的连接,节点0可以到达的节点是0和1和2,不可以到达节点3,进行更新:
arr[0]=0123;
arr[1]=0123;
arr[2]=0123;
arr[3]=0123;
同样到这里了,我们依旧没有判断闭环
(4)遍历到路径④:这个路径是节点2和节点3之间的连接,因为节点2可以到达的节点是0、1、2、3,能够到达节点3,因为已经能够到达了,又一次进行了连接,所以产生了闭环,直接跳出就可以了。
arr[0]=0123;
arr[1]=0123;
arr[2]=0123;
arr[3]=0123;
代码实现
public class 并查集 {
static String[] point;
static boolean flage = false;
public static void main(String[] args)
{
@SuppressWarnings("resource")
Scanner in =new Scanner(System.in);
System.out.println("Please enter the number of points:");
String s1 = in.nextLine();
int x = Integer.valueOf(s1);
point = new String[x];
for(int i=0;i<x;i++)
{
point[i]=i+"";
}
System.out.println("Please enter the number of edges:");
String s2 = in.nextLine();
int y = Integer.valueOf(s2);
int[][] edgs= new int [y][2];
for(int i=0;i<y;i++)
{
System.out.println("Please enter the node connected by the "+(i+1)+" edge (separated by space).:");
String[] str = in.nextLine().split(" ");
edgs[i][0]=Integer.valueOf(str[0]);
edgs[i][1]=Integer.valueOf(str[1]);
}
for(int i=0;i<y;i++)
{
if(union(edgs[i][0],edgs[i][1])==0)
{
flage=true;
break;
}
}
if(flage)
{
int index = 0;
System.out.println("Have");
for(String s:point)
{
System.out.println("The node that node "+index+" can reach"+":\t"+s);
index++;
}
}else
{
System.out.println("Haven't");
}
}
public static int union(int x,int y)
{
String s = String.valueOf(y);
if(point[x].contains(s))
{
return 0;
}else
{
String temp =point[x];
point[x]=point[x]+s;
point[y]=point[x];
fill(temp,point[x]);
return 1;
}
}
public static void fill(String s,String str)
{
for(int i=0;i<point.length;i++)
{
if(point[i].contains(s))
{
point[i]=str;
}
}
}
}
控制台实例
Please enter the number of points:(请输入节点个数)
4(4个)
Please enter the number of edges:(请输入路径个数)
3(3条)
Please enter the node connected by the 1 edge (separated by space).:(第一条路径)
0 1(节点0和节点1之间有路径)
Please enter the node connected by the 2 edge (separated by space).:(第二路径)
1 2(节点1和节点2之间有路径)
Please enter the node connected by the 3 edge (separated by space).:(第三条路径)
0 3(节点0和节点3之间有路径)
Haven't(不含有闭环)
Please enter the number of points:(请输入节点个数)
4(4个)
Please enter the number of edges:(请输入路径个数)
4(4条)
Please enter the node connected by the 1 edge (separated by space).:(第一条路径)
0 1(节点0和节点1之间有路径)
Please enter the node connected by the 2 edge (separated by space).:(第二路径)
1 2(节点1和节点2之间有路径)
Please enter the node connected by the 3 edge (separated by space).:(第三条路径)
0 3(节点0和节点3之间有路径)
Please enter the node connected by the 4 edge (separated by space).:(第四条路径)
2 3(节点2和节点3之间有路径)
Have(存在闭环)
The node that node 0 can reach: 0123(节点0可以到达的节点:0123)
The node that node 1 can reach: 0123(节点1可以到达的节点:0123)
The node that node 2 can reach: 0123(节点2可以到达的节点:0123)
The node that node 3 can reach: 0123(节点3可以到达的节点:0123)
总结
并查集并不是很难理解,可以说它是一个冷门必考点,基本上每一届都会有,但是没有那么多,不像深搜广搜那么频繁,能用的话也是要和其它算法相结合起来一起用,自己很难说能够单独解出来一道题。