朋友圈
前言
本题所用的主要算法是并查集
并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等。
1.并查集的代码实现
并查集主要的实现方法是通过父节点数组vector f() 来实现父亲节点和儿子节点的联系,比如 f[3]=4 就是说3的父亲节点是4,而并查集想要完成的是将目标节点放在一个集合中,那就可以将他们的根节点(也可以称之为祖先节点)都变成一样的,这样在判断集合的时候,只要通过find函数(返回该节点祖先节点的函数)就可以找到祖先,而在同一个祖先下的节点就归在一个集合中,merge函数就是将两个节点的祖先节点设置为一个,从而做到将两个节点合并到同一个集合中。
vector<int> f(30001)
int find(int p)
{
if (p == f[p])
return p;
else {
return find(f[p]);
}
}
void merge(int x, int y)
{
int a = find(x), b = find(y);
if (a != b)
f[a] = b;
}
2.本题的解决代码
本题是一个并查集的经典例题,只要将并查集理解解决本题就很容易了,所以具体的解决步骤在此不再赘述。
#include<iostream>
#include<vector>
using namespace std;
vector<int> f(30001), cnt(30001), a(30001);
int find(int p)
{
if (p == f[p])
return p;
else {
return find(f[p]);
}
}
void merge(int x, int y)
{
int a = find(x), b = find(y);
if (a != b)
f[a] = b;
}
int main()
{
int N, M;
cin >> N >> M;
for (int i = 1;i <= N;i++)
f[i] = i;
while (M--)
{
int x;
cin >> x;
for (int i = 0;i < x;i++)
cin >> a[i];
for (int i = 1;i < x;i++)
merge(a[0], a[i]);
}
int max = 0;
for (int i = 1;i <= N;i++)
{
int u = find(i);
cnt[u]++;
if (cnt[u] > max)
max = cnt[u];
}
cout << max;
return 0;
}
3.并查集的路径优化算法
在我们做题的时候其实不难发现,我们在判断一个人的根节点时,每判断一个人就需要从这个人开始一直遍历到他的祖先节点,而我们可以假设,后面的节点有一个是这个节点的父系节点(意思就是这个节点和他的祖先节点之间的一个节点),寻找这个节点的根节点的时候一样需要一直遍历到根节点,而很显然上述两个节点在通过find函数寻找根节点的过程中走了重复的路径,这就造成了时间效率的浪费,在一些时间复杂度要求严格的题目里就无法通过,于是就产生了路径压缩算法。
算法代码如下:
int find(int p)
{
if (p == f[p])
return p;
else {
return f[p] = find(f[p]);
}
}
我们会发现其实压缩和不压缩在代码上区别不大,就是在返回值上做了一些改动
原本是:
return find(f[p]);
现在是:
return f[p] = find(f[p]);
这样可以省去之前节点已经走过的路程,每对一个节点进行find操作,就会将此节点的所有父系节点(就是从此节点到根节点之间的所有节点)他们的父亲节点全部设置为根节点,接下来如果碰到此节点的父系节点的find操作就可以一步找到根节点。
关于并查集还有非递归的实现方法,感兴趣的同学可以自己去学习一下。