并查集的分析
并查集是一种非常精巧而且实用的数据结构,它主要用于处理一些不相交集合合并的问题,经典例子有联通子图,最小生成树之克鲁斯卡尔算法和最近公共祖先等…
常见操作
- 合并两个集合,将一个集合的根节点作为另一个集合的根节点
- 查找某个元素属于哪个集合
- 判断两个元素是否属于同一集合,只需要判断他们的根节点是否相同即可
代码
并查集代码一般有三个函数,他们分别是
- 初始化函数初始化每个点为独立的集合,一般来说初始化都是写在主函数里面
for (int i = 1; i <= n; ++i)
pre[i] = i;
- 查找函数,查找一个元素所在的集合,沿着父节点一直找下去,直到找到根节点为止
int find(int x)//路径压缩
{
if (x != pre[x])
//如果当前节点不等于父节点那么就递归查找,知道找到为止,并把根节点赋给当前节点实现路径压缩功能
pre[x] = find(pre[x]);
return pre[x];
}
- 合并函数,将一个集合的根节点指向另一个集合的根节点
void join(int x, int y)
{
int a = find(x);
int b = find(y);
if (a != b)
pre[a] = b;//让b等于a的父节点
}
这里强调一下,如果没有返回值那么函数设为void型,有时候使用int定义函数但是又没有返回值会报错
并查集例题
题目描述
在某个城市中住着n个人,现在给定关于这n个人的m条信息(即某2个人认识)。
假设所有认识的人一定属于同一个单位,请计算该城市有多少个单位?
输入
第1行的第1个值表示总人数n,第2个值表示总信息数m;第2行开始为具体的认识关系信息
输出
单位的个数
样例输入
10 4
2 3
4 5
4 8
5 8
样例输出
7
这是一道并查集模板题,见代码~
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int dp[1010][1010];
int pre[maxn];
int m, n;
int find(int x)
{
if (x != pre[x])
pre[x] = find(pre[x]);
return pre[x];
}
void join(int x, int y)
{
int a = find(x);
int b = find(y);
if (a != b)
pre[a] = b;
}
int main()
{
while (~scanf("%d%d", &n, &m))
{
int ans = 0;
for (int i = 1; i <= n; ++i)
pre[i] = i;
for (int i = 1; i <= m; ++i)
{
int x, y;
scanf("%d%d", &x, &y);
// if (find(x) != find(y))
join(x, y);
}
for (int i = 1; i <= n; ++i)
if (pre[i] == i)//去查找一共还剩下几个集合
ans++;
printf("%d\n", ans);
}
return 0;
}
hdu1213
杭电1213传送门
这也是一道经典的并查集的模板题,与上题几乎一样,由于我们初始化的时候把每个点都设为独立的集合,之后我们又进行了合并的操作,那么我们就可以看看有哪些点的父节点没有变,有几个那么就有几个集合
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int pre[maxn];
int find(int x)
{
if (x != pre[x])
pre[x] = find(pre[x]);
return pre[x];
}
void join(int x, int y)
{
int a = find(x);
int b = find(y);
if (a != b)
pre[a] = b;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int ans = 0;
int n, m, x, y;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
pre[i] = i;
for (int i = 1; i <= m; ++i)
{
scanf("%d%d", &x, &y);
join(x, y);
}
for (int i = 1; i <= n; ++i)
if (pre[i] == i)
ans++;
printf("%d\n", ans);
}
return 0;
}
poj1611
这也是一道比较经典的例题,只不过稍微有些变形,其实大概思路还是一样,由于poj是一个十分古老的在线oj,所以它不支持万能头文件~
在我们把该合并的合并之后,我们只需要差找哪些节点与0号患病者在一个集合就行,同样也是一句代码解决
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 10;
int dp[1010][1010];
int s[maxn];
int pre[maxn];
int m, n;
int find(int x)
{
if (x != pre[x])
pre[x] = find(pre[x]);
return pre[x];
}
void join(int x, int y)
{
int a = find(x);
int b = find(y);
if (a != b)
pre[a] = b;
}
int main()
{
while (~scanf("%d%d", &n, &m))
{
if (n == 0 && m == 0)
break;
int ans = 0, cnt;
for (int i = 0; i <= n; ++i)
pre[i] = i;
while (m--)
{
int x, y;
scanf("%d", &cnt);
scanf("%d", &x);
for (int i = 1; i < cnt; ++i)
{
scanf("%d", &y);
join(x, y);
}
}
for (int i = 0; i < n; ++i)
if (find(0) == find(i))
ans++;
printf("%d\n", ans);
}
return 0;
}
凡有告别,便有重逢,今天的离别只是为了明天更好的再聚~ |
---|