并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
例题:亲戚
【问题描述】
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
【输入】
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
【输出】
P行,每行一个‘Yes’或‘No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。
① 由此用某个元素所在树的根结点表示该元素所在的集合;
② 判断两个元素时候属于同一个集合的时候,只需要判断他们所在树的根结点是否一样即可;
③ 也就是说,当我们合并两个集合的时候,只需要在两个根结点之间连边即可。
步骤: 1:初始化根节点: void initroot() { for(int i = 1; i < =n; i++) root[i] = i; }//n个人 2:寻找根节点 int find(int x){ if(root[x]==x) return x; return root[x]=find(root[x]);//父节点的的父节点、、直至找到最高处的父节点,即root[i]=i的 } 或是简单点的: int find(int x){return root[x] == x? x: root[x] = find(root[x]);} 3:合并两个集合(将两个的根节点连在一起就行) void mgroot(int x,int y){///合并两个集合时时直接找到根节点修改根节点即可,使得他们的根节点相同 int xs=find(x); int ys=find(y); if(xs!=ys) root[xs]=ys; } 4:求得最少集合数 for(int i = 1; i <= n; i++){//找父亲为自己的结点的个数,就可以得到有几种集合 if(root[i] == i) count++; }
输出结果如下: | 代码: #include <bits/stdc++.h> #include<iostream> using namespace std; const int N = 100000; int root[N];
void initroot(int n) {///初始化 for(int i = 1; i <=n; i++) root[i] = i; } int find(int x){ if(root[x]==x) return x; return root[x]=find(root[x]);//父节点的的父节点、、直至找到最高处的父节点,即root[i]=i的 } void mgroot(int x,int y){///合并两个集合时时直接找到根节点修改根节点即可,使得他们的根节点相同 int xs=find(x); int ys=find(y); if(xs!=ys) root[xs]=ys; } int main() { int n,m; int a,b; int count=0; cout<<"人数 总关系对数:"<<endl; cin>>n>>m;///得到人数和总共的关系对数; initroot(n); cout<<"请输入关系:"<<endl; for(int i=0;i<m;i++){ cin>>a>>b; mgroot(a,b);///增加a-b关系,即a与b有联系,那么a在的集合与b在的集合可以合并成一个集合 } for(int i = 1; i <= n; i++){//找父亲为自己的结点的个数,就可以得到有几种集合 if(root[i] == i) count++; } cout<<"最少集合数:"<<count<<endl; cout<<"请输入测试数据:a b:"<<endl; int c,d; while(1){ cin>>c>>d; if(find(c)==find(d)) cout<<"yes"<<endl; else cout<<"no"<<endl; } return 0; } |