这是我再一次对并查集的理解,这次比上次理解的更加深刻了
这是上一次的**并查集**
题目是这样
输入
输入的第一行包含两个用空格隔开的正整数n和k,其中n不超过100,k不超过n-1。
之后的k行中,每行包含两个用空格隔开的正整数x和y,表示将x元素所在的集合和y元素所在的集合合并至同一个集合。保证x和y均在1至n之间。
最后一行中,包含两个正整数,表示需要判断是否在同一个集合的元素编号。
输出
共一行,包含字符串“YES”或“NO”,“YES”表示需判断的元素在同一个集合中,“NO”表示不在同一个集合中。请注意不需要输出引号,且行尾输出换行。
样例输入
5 2
1 3
2 3
1 2
样例输出
YES
主要的思想呢?是查找每个点的父结点,如果父结点相同,那就不需要合并了,如果父结点相同,那么就需要合并了,
比如1的父结点是2,2的父结点是3,3是自己的父结点,
这个时候我们想合并 1和5的两个子集,1的最终父结点是2,而5的最终父结点是5,明显不同,所以就需要合并了,
先不谈路径压缩,
int find_root(int x)
{
while(vis[x] != -1){
x = vis[x];
}
return x;
}
这样找父结点的就行了,但这样会有一个问题,1找2,2找3,要执行两次,这种数据小的看不出来什么变化,但万一数据很大呢?
我们就可以把1的父结点直接变成3,这样一下子就找到了,这就是路径压缩,而上面的那段程序不可以,
我们可以试试这样
int find_root(int x)
{
if(vis[x] != -1) return vis[x] = find_root(vis[x]);//这里最关键
else return x;
}
大家可以自行比较两个程序的不同之处,**这里为什么要用递归呢?**因为父结点是最后被找到的,
代码如下 :
#include <stdio.h>
int vis[105];
//vis[i] = a
//i的根结点是a
/*
int find_root(int x)
{
while(vis[x] != -1){
x = vis[x];
}
return x;
}
*/
int find_root(int x)
{
if(vis[x] != -1) return vis[x] = find_root(vis[x]);
else return x;
}
void merge(int x,int y)
{
int root_x = find_root(x);
int root_y = find_root(y);
if(root_x == root_y){
return;
}else {
//merge
vis[x] = y;
}
}
int main()
{
int n,k;
scanf("%d %d",&n,&k);
for(int i = 1;i <= n;i++){
vis[i] = -1;
}
for(int i = 1;i <= k;i++){
int x,y;
scanf("%d %d",&x,&y);
merge(x,y);
}
int x,y;
scanf("%d %d",&x,&y);
if(find_root(x) == find_root(y)){
printf("YES\n");
}else {
printf("NO\n");
}
/*
for(int i = 1;i <= n;i++){
printf("%d ",vis[i]);
}
*/
return 0;
}