今天我们来学习并查集的使用,用一个具体的例题来做:
#include<iostream>
#include<vector>
using namespace std;
vector<int> father(101);
int n;
void init(){
for(int i=1;i<=n;++i)father[i]=i;
}
int find(int u){
return u==father[u]?u:father[u]=find(father[u]);
}
bool isSame(int u,int v){
u=find(u);
v=find(v);
return u==v;
}
void join(int u,int v){
u=find(u);
v=find(v);
if(u==v)return;
father[v]=u;
}
int main(){
int m,s,t;
cin>>n>>m;
init();
for(int i=0;i<m;++i){
cin>>s>>t;
join(s,t);
}
int source,destination;
cin>>source>>destination;
if(isSame(source,destination)){
cout<<1<<endl;
return 0;
}
else{
cout<<0<<endl;
return 0;
}
}
事实上,这就是一个并查集的模板题,我们不妨把并查集的模板放上来:
int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]); // 路径压缩
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
总的来说我们有四个函数:初始化,查找,判断是否同根,联合。初始化只是先构建一个大体的映射关系,而查找就是进行一个路径压缩,如何理解呢?准确地说就是去找根的一个过程,我们判断u和father[u]是否相等,如果相等说明u本身就是一个根节点,可如果不是,说明u的根节点是一个其他节点,那这个时候我们就要去找u的根节点了:我们向上一层,去找father[u]的根节点并形成递归;
判断同根很好理解,最后的联合则是先找到两个节点的根节点,如果发现两个根相同说明二者在同一个集合中,不然我们就让u成为v的父节点,也就是说让v的集合全部放在u下面,形成v->u的路径。
一般来说,我们会在针对判断两个点是否在同一个集合里的题使用并查集,尤其是判断比如我们的图论题中两个点是否有存在路径。