并查集
并查集一种简单的集合表示。
- 通常用树的双亲表示法来作为存储结构。
根据语义来讲,并查集支持下面的操作:
- 合并:合并两个集合
- 查找:判断两个元素是否在一个集合
并查集的实现
可以直接使用一个数组
int father[N];
其中father[i]
表示元素i的父亲结点,而父亲结点本身也是这个集合内的元素。
例如:
father[1] = 2
就表示元素1的父亲结点为2。father[i] = i
,则说明元素i是根结点。
初始化
一开始,每个元素都是独立的集合。
举个例子:
现在由集合A = {1,2,3,4,5,6,7,8,9},初始化操作就是把这一个集合变为
{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}
for(int i = 0;i<=N;i++){
father[i] = i;
}
查找
同一个集合只有一个根节点,那么查找就是给对指定的结点寻找其根结点。
int findFather(int x){
while(x != father(x){ // X!= father(x) 自己不是根节点
x = father(x); //把现在x的根节点赋给x,向着根节点不断递推
}
return x;
}
也可以用递归方式实现
int findFather(int x){
if(x == father(x)){
return x;
}
else return findFather(father(x));
合并
把两个集合合并成一个集合。
具体实现上一般是判断两个元素是否为同一个集合的,只有当两个元素属于不同集合时才合并,而合并的过程一般是把其中一个集合的根结点的父亲指向另一个集合的根结点
void Union(int a,int b){
int faA = findFather(a);
int faB = findFather(b);
if(faA != faB){
father[faA] = faB;
}
}
为什么是要根结点,那个元素其中一个a的father为b不就好了,a和b就联系起来了,事实上不行,假设原本father[a] = c;现在修改成了father[a] = b;那么a和c的联系就断了,所以两个集合并没有合并起来。
路径压缩
为什么要用并查集来存储,是为了加快查找速度。
但是,如果某并查集是个链式结构, 那查找速度仍然是O(n)。
所以我们把当前查询结点的路劲上的所有结点的父亲都指向根节点。查询的复杂度可降为O(1)
上面的findfather是不断获取父亲结点来最终达到根节点的。
int findFather(int x){
int a = x;
while(x != father[x]){
x = father[x];
}
// 现在x存放的是根节点,下面把路径上的所有结点的father都改为根结点
while(a != father[a]){
int z = a;
a = father[a]; //a回溯父亲结点
father[z] = x; //将原先结点a的父亲改为根结点x
}
return x;
}
这样查找时就可以把寻找根结点的路劲压缩了。
下面是递归的写法
int findFather(int v){
if(v == father[v]){
return v;
}
else{
int F = findFather(father[v]);
father[v] = F;
return F;
}
}
问题
算法笔记【好朋友】
问题
- a 和 b是好朋友,那么b 和 a就是好朋友
- a和b是好朋友,b和c是好朋友,a和c也就是好朋友
- 第一行 输入为n和m,表示数码宝贝的个数和好朋友的组数
- 有m行,每行两个整数,表示数码宝贝a和b是好朋友,数码编号从1到n
// 问题 a 和 b是好朋友,那么b 和 a就是好朋友
// a和b是好朋友,b和c是好朋友,a和c也就是好朋友
// 第一行 输入为n和m,表示数码宝贝的个数和好朋友的组数
// 有m行,每行两个整数,表示数码宝贝a和b是好朋友,数码编号从1到n
#include<iostream>
#include<vector>
using namespace std;
class unionset {
public:
vector<int> father;
vector<bool> isRoot;
int findFather(int x) {
int a = x;
while (x != this->father[x]) { // 因为使用vector进行push_back的,所以是从0开始
x = this->father[x];
}
// 路径压缩
while (a != this->father[a]) {
int z = a; //a是目前的结点
a = this->father[a]; //a是之前a的父亲
this->father[z] = x; //让a的父亲为x
}
return x;
}
void Union(int a, int b) {
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB) {
father[faA] = faB;
}
}
void init(int n) {
for (int i = 0; i <= n; i++) {
father.push_back(i);
isRoot.push_back(false);
}
}
};
int main() {
int n, m, a, b;
cin >> n >> m;
unionset set;
set.init(n);
set.findFather(2);
for (int i = 0; i < m; i++) {
cin >> a >> b;
set.Union(a, b);
}
for (int i = 1; i <= n; i++) {
set.isRoot[set.findFather(i)] = true;
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans += set.isRoot[i];
}
cout << ans << endl;
return 0;
}