这篇文章有参考另一个小姐姐博客,她写的比较详细☞ https://blog.youkuaiyun.com/xu1105775448/article/details/82077944
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。<来自百度百科>
主要操作
初始化
-
把每个点所在集合初始化为其自身
查找
-
查找元素所在集合,即根节点
合并
-
将两个元素所在集合合并为一个集合
例题讲解
这里以 9.3 号贝壳技术笔试的 [家族关系] 例题演示并查集,一道题可能并不能覆盖整个数据结构的思路,其他的还需要多多刷题
题目描述:
有一个族谱,上面有 n 个人,整个家族关系中有且只有一个祖先(这里可以理解为根),除了根以外的所有人有且只有一个父亲,大概关系有以下几种
问题:
给出两个人,如 A,B,输出 A 和 B 的关系
-
如果 A 是 B 的祖先,输出 1
-
如果 B 是 A 的祖先,输出 2
-
如果没有祖先关系,输出 0
输入描述:
-
第一行一个整数 n,表示这个族谱里的人数
-
接下来 n 行,每行输出两个数字,第一个表示当前的这个人,第二个数字表示他的父亲是谁 (根的父节点使用 -1 表示,有且只有一个)
-
第 n+2 行是一个整数 m, 表示询问关系的组数
-
接下来的 m 行,给出 A,B 两个人,就是求这两个人的关系
输出描述:
-
一共 m 行,每行输出两者关系
输入举例:
10
1 -1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
10 1
2 10
5
1 3
2 5
7 10
10 1
2 1
输出举例:
1
0
0
2
2
首先根据题目的输入,我们可以大概理清楚这十个人的关系如下,1 是所有人的祖先,3~10是没有祖先关系的,10 是 2 的祖先
这里需要强调,其实很多人看到这道题都想使用树来解答,但是个人觉得树是不可以的,因为这个输入,并不能够直到每个节点有多少个子树,而且如果要维护结构体,占空间也会很大~
初始化
-
这里使用一个一位数组保存所有的关系,我们看到了,人是不重复的,我们可以使用数组的索引来表示,他的父亲可以是对应索引的值(即使用输入数据的第一列为索引,第二列为索引的值)
-
为了方便,我们创建一个 n+1 大小的数组 v,0 号索引的位置空下不使用
查找
我们以单次输入来讲解,输入使用 A,B表示,可以大致分为三种情况
-
v[A] = -1,这表示 A 就是根,那他一定是任意家族成员的祖先,可以直接返回 1
-
v[B] = -1,这表示 B 就是根,那他一定是任意家族成员的祖先,可以直接返回 2
-
剩下情况,关键点 -> 以当前数组值作为下一个查找的索引
-
有关系,以 2,1 为例: v[2] = 10,表示 10 是 2 的祖先, v[10] = 1,表示 1 是 10 的祖先,那自然 1 也就是 2 的祖先
-
无关系,以 7,10 为例: v[7] = 1,表示 1 是 7 的祖先,v[1] = -1,表示 1 是根,这个时候还没有找到 7 和 10 的关系,表示两者并无关系
-
(这个题比较简单,不需要合并)
完整代码
#include <iostream>
using namespace std;
#include <vector>
int IsParent(vector<int>& v,int a,int b)
{
if(v[a] == -1)
{// a 是根节点
return 1;
}
else if(v[b] == -1)
{// b 是根节点
return 2;
}
else
{
int cur = v[a];
while(cur != -1)
{
if(cur == b)
{
return 2;
}
cur = v[cur];
}
cur = v[b];
while(cur != -1)
{
if(cur == a)
{
return 1;
}
cur = v[cur];
}
return 0;
}
}
int main()
{
int n; //表示人员个数
cin>>n;
vector<int> v(n+1,0); //位置0保留,不使用
int a,b;
for(int i = 0; i < n; ++i)
{
cin>>a;
cin>>b;
v[a] = b;
}
vector<int> vv;
int m; // 询问个数
cin>>m;
for(int i = 0; i < m; ++i)
{
cin>>a;
cin>>b;
int res = IsParent(v,a,b);
vv.push_back(res);
}
for(size_t k = 0; k < vv.size(); ++k)
{
cout<<vv[k]<<endl;
}
return 0;
}
题目中的坑:
题目给出输入的 1 < n,m <= 40000,最开始我在主线程栈上建立一个 n+1 的数组,但是最后智跑过了 75%,赛码网调试报错是 Runtime out(大概是这个样子),意识到是内存上或者时间上的问题,最后把数组定义到全局区,直接定义 400001 大小的数组,直接 AC 了,栈上的空间太少了,静态区却足够大,所以我认为这是这个题一个坑吧...