并查集解决什么问题
并查集常用来解决连通性问题。
大白话就是当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。
原理
既然是查找是否在同一个集合中,那么这个集合是怎么构建的呢?用一维数组来表示一个有向图,对于father[u]=v,来说,u指向了v,那么假如father[v]=k,这意味u,v,k是连通的,所以如何表示连通呢?
bool isSame(int u,int v){u=find(u);//寻找father[u],即u的根(指向)v=find(v);if(u==v) return true;//意味着u,v他们指向了同一个结点,所以在一个集合中return false;
}
而这个find()其实就是递归的寻找father,直到找到根为止
int find(int u){if(u==father[u]) return u;//自己指向自己,根else return find(father[u]);
}
如果最后的根是自己指向自己,那么初始化就是这样
void init(){for(int i=1;i<=n;i++){//一般结点从1开始father[i]=i;}
}
那么集合如何构建呢?
void join(int u,int v){u=find(u);v=find(v);if(u==v) return;//根相同,没必要加入了father[v]=u;//设置根
}
路径压缩
在查找结点的根的时候,我们使用了递归的方式,但是查找次数边多之后,会发现这个过程是重复的,会浪费很多时间,就像这样,1<-2<-3<-4,如果我们查找4的根就需要递归好多次,如果我们变成这样呢?1<-2,1<-3,1<-4,每次查找根只需要一次就够了。这就是路径压缩。所以我们的find函数应该这样写
// 并查集里寻根的过程
int find(int u) {if (u == father[u]) return u;else return father[u] = find(father[u]); // 路径压缩
}
误区
这些代码中join和isSame有一段很相似,但在join中不能调用isSame,就像这样
// 判断 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) {if (isSame(u, v)) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}
假设执行join(1,2),join(3,2),他的过程是这样的,
先查找1,2的根为 1,2,所以构建1<-2,但是在执行join(3,2)时,3的根是3,而2的根是1,那么在isSame中,这个函数返回false,意味着这两个值不在一个集合中。然而这并不对,我们想要的是这两个数在同一个集合中。
那么如果代码像之前那样呢?
在构建完1<-2之后,查找3的根是3,2的根是1,然后又构建了一个3<-1。这个时候再去执行isSame的话,会发现1的根是3,3的根是3,3==3,所以两个数在同一个集合中。
好了,原理大概就这样,总结一下,并查集的大致套路
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;
}
既然如此,那么就来刷一道题
题目:寻找存在的路径
107. 寻找存在的路径 (kamacoder.com)
输入描述
第一行包含两个正整数 N 和 M,N 代表节点的个数,M 代表边的个数。
后续 M 行,每行两个正整数 s 和 t,代表从节点 s 与节点 t 之间有一条边。
最后一行包含两个正整数,代表起始节点 source 和目标节点 destination。
输出描述
输出一个整数,代表是否存在从节点 source 到节点 destination 的路径。如果存在,输出 1;否则,输出 0。
题目分析:
标准的并查集问题
#include<iostream>
#include<vector>using namespace std;int n;
vector<int> father=vector<int>(101,0);void Init(){for (int i = 1; i <= n; i++) father[i] = i;
}
int find(int u){if(u==father[u]) return u;return father[u]=find(father[u]);//递归查找根,这一步进行了路径压缩,将所有结点指向根
}void join(int u,int v){u=find(u);v=find(v);if(u==v) return;//具有相同的根,同一个集合father[v]=u;//把v作为u的父节点加入集合
}
bool isSame(int u,int v){u=find(u);v=find(v);return u==v;
}int main(){int m,s,t,source,destination;cin>>n>>m;Init();while(m--){cin>>s>>t;join(s,t);}cin >> source >> destination;if (isSame(source, destination)) cout << 1 << endl;else cout << 0 << endl;
}
对于更详细的解析与其他语言的代码块,可以去代码随想录上查看。
代码随想录 (programmercarl.com)