并查集从名字中就能知道两种操作,合并和查找。它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等。并查集其实本质上就是树形结构,如图
如何存储这个结构呢?用一个数组f存储, 例如h的双亲节点是d即 f[ h ] = d, f[ d ] = b, f[ b ] = a;f[ a ] = a;就是这种存储结构。
并查集主要的操作合并和查找,但是在合并和查找之前,还需要做初始化操作,如图
for(int i = 1; i <= n; i++)f[i] = i;
为什么这么初始化稍后讲解。
然后是查找操作,如何进行查找呢?
int Find(int x){
while(x != f[x])
x = f[x];
return x;
}
查找操作其实就像遍历一样从数的x这个位置遍历到根节点就结束查询。如图
例如我想从x = 4开始查询,f[ 4 ]存储的是5, 所以 4 != f[ 4 ] , 即x != f[ x ]; f[ 5 ]存储的是3, 所以 5!= f[ 3 ] , 即x != f[ x ];f[ 3 ]存储的是1, 所以 3 != f[ 3 ] , 即x != f[ x ];f[ 1 ]存储的是1, 所以 1 != f[ 1 ] , 即x = f[ x ];然后返回x.上述过程就是查询的过程,子节点存的是双亲结点,直到查询到根节点结束查询。
如何进行合并操作呢?就是查找x, y的根节点是否是一个,如果是一个那就不需要合并了,因为他们就是在一棵树上,如何根节点不是一个,那就把他们合并, 把一个的根节点fx指向另一根节点fy, 此时fx就不是根节点,只有fy是根节点。
void Join(int x, int y){
int fx = Find(x), fy = Find(y);
if(fx != fy)
f[fx] = fy;
}
这个就是合并操作了,又回到最开始的问题,初始化的作用,初始化就是确定根节点,如果f[ x ] == x, x就是根节点,否则无法找到根节点。
虽然并查集的三个骚操作都已经大概知道了,但是你有没有想过,如果不断合并结果就是一条线,查找时候就灰常的麻烦,所有就有了压缩路径,就比如上图的4, 5,在查找的时候都是查找到根部,那么有的路径其实可以压缩,所以4就不惜的经过3和5,直接指向1,同样的你4都不惜的我5也不惜的经过3了,所以3直接指向1了,所以所有数的双亲结点都是1,这么就做到压缩路径了。
int Find(int x){
int p = x, tmp;
while(x != f[x])
x = f[x];
while(p != x){
tmp = f[p];
f[p] = x;
p = tmp;
}
return x;
}
How Many Tables HDU 1213
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1213
题意:互相认识的人坐在同一个桌子,有几组互相认识的人就分成几组,只有在某一组有一个认识的人,那么你就跟整组相互认识。
题解:本题就是并查集的典型例题,只要查找有几个根节点就可以了,一个根节点就是一组相互认识的人。
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
#define N 1005
int f[N];
int Find(int x){
int p = x, tmp;
while(x != f[x])
x = f[x];
while(p != x){
tmp = f[p];
f[p] = x;
p = tmp;
}
return x;
}
void Join(int x, int y){
int fx = Find(x), fy = Find(y);
if(fx != fy)
f[fx] = fy;
}
int main(){
int T, n, m;
scanf("%d", &T);
while(T--){
scanf("%d %d", &n, &m);
int fx, fy;
for(int i = 1; i <= n; i++)f[i] = i;
while(m--){
scanf("%d %d", &fx, &fy);
Join(fx, fy);
}
int sum = 0;
for(int i = 1; i <= n; i++){
if(f[i] == i)sum++;
}
printf("%d\n", sum);
}
return 0;
}
The Suspects POJ 1611
题意:统计跟0有关的数字,只要跟0在一个树中那么这个数就跟0,有关系,此外一个数跟0有关的数有关,那么跟0也有关系。
题解:把所有的数据都正在存在f数组中,然后查找根和0的跟是一样数的个数,就是跟0有关系的数即被感染。
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define N 30005
int f[N];
int Find(int x){
int p = x, tmp;
while(x != f[x])
x = f[x];
while(p != x){
tmp = f[p];
f[p] = x;
p = tmp;
}
return x;
}
void Join(int x, int y){
int fx = Find(x), fy = Find(y);
if(fx != fy)
f[fx] = fy;
}
int main(){
int n, m;
while(~scanf("%d %d", &n, &m)){
if(n == 0 && m == 0)break;
for(int i = 0; i <= n; i++)f[i] = i;
int k, t, a;
while(m-- ){
scanf("%d %d", &k, &t);
for(int i = 1; i < k; i++){
cin>>a;
Join(t, a);
t = a;
}
}
int pos = Find(0);
int sum = 0;
for(int i = 0; i < n; i++){
if(Find(i) == pos)sum++;
}
printf("%d\n", sum);
}
}
P1111 修复公路 洛谷 1111
链接:https://www.luogu.org/problemnew/show/P1111
题意:同时开始修路,告诉村庄x, y间的路,多少天能修好,x和y之间可以通过别的地方到达,问最短多少天任意地点都可相互到达。
题解:此题也是并查集的题,但是在数据处理的时候有些不同,先把时间按照从小到大的顺序,然后在不断的合并,如能合并记录合并多少次,一次合并就会产生一条路num,同时更新此时时间的最大值。如果路num > n-1那么任意路之间可以相互到达。输出此时时间的最大时。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 1005
#define maxn 10000000
//此题思路就是并查集算法,如果这两个数不是一个祖先,那么就合并
//但是此过程中先把a排序,为什么排序呢,因为排完序之后合并的时间应该是从最短的开始
int f[N];
struct Node{
int x, y, z;
}a[100005];
bool cmp(Node A, Node B){
return A.z < B.z;
}
int Find(int x){
int p = x, tmp;
while(x != f[x])
x = f[x];
while(p != x){
tmp = f[p];
f[p] = x;
p = tmp;
}
return x;
}
int main(){
int n, m;
cin>>n>>m;
for(int i = 1; i <= n; i++)f[i] = i;
for(int i = 1; i <= m; i++){
cin>>a[i].x>>a[i].y>>a[i].z;
}
sort(a+1, a+m+1, cmp);
int cn = 0, ans = -1;
for(int i = 1; i <= m; i++){
int fx = Find(a[i].x), fy = Find(a[i].y);
if(fx != fy){
f[fx] = fy;
cn++;
ans = max(ans, a[i].z);
}
}
if(cn >= n-1)cout<<ans<<endl;
else cout<<-1<<endl;
return 0;
}
畅通工程 HDU 1232
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1232
题意:为了使所有的地方都可以相互到达,还需要修多少条路。
题解:其实可以转换成有多少组关系,如果有n组,没组之间没有关系,那么就需要在建立n-1个关系。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<iostream>
using namespace std;
#define N 500004
int f[N];
int Find(int x){
int p = x, tmp;
while(f[x] != x){
x = f[x];
}
while(p != x){
tmp = f[p];
f[p] = x;
p = tmp;
}
return x;
}
void Join(int x, int y){
int fx = Find(x), fy = Find(y);
if(fx != fy)
f[fx] = fy;
}
int main(){
int n, m;
while(~scanf("%d", &n)){
if(n == 0)break;
scanf("%d", &m);
for(int i = 1; i <= n; i++){
f[i] = i;
}
int fx, fy;
for(int i = 1; i <= m; i++){
scanf("%d %d", &fx, &fy);
Join(fx, fy);
}
int sum = 0;
for(int i = 1; i <= n; i++){
if(f[i] == i)sum++;
}
printf("%d\n", --sum);
}
return 0;
}
Corporative Network UVALIVE 3027
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<iostream>
using namespace std;
#define N 20005
int f[N], d[N];
int Find(int x)
{
if(f[x] != x)
{
int root = Find(f[x]);
d[x] += d[f[x]];
return f[x] = root;
}
return x;
}
int main()
{
int T, n, u, v;
char a;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
f[i] = i;
d[i] = 0;
}
while(scanf("%c", &a) && a != 'O')
{
if(a == 'E')
{
scanf("%d", &u);
Find(u);
printf("%d\n", d[u]);
}
if(a == 'I')
{
scanf("%d %d", &u, &v);
f[u] = v;
d[u] = abs(u-v)%1000;
}
}
}
return 0;
}