定义
利用一个数组实现并查集
int father[N];
其中father[i]表示元素i的父亲结点,比如father[1] = 2说明1的父亲结点是元素2;而如果father[i] == i则说明i是该集合的根节点。
father[1] = 1;
father[2] = 1;
father[3] = 2;
father[4] = 2;
父亲关系图:
1
|
2
/ \
3 4
基本操作
- 初始化
刚开始每个元素都是独立的集合
for (int i = 0; i <= N; i++)
father[i] = i;
- 查找
因为一个集合只有一个根节点,所以查找就是对给定的结点找其根节点的过程,可以用递归或者递推。思路是反复寻找父亲结点,直到找到根节点(father[i] == i)
递推:
int findFather(int x)
{
while (x != father[x])
x = father[x];
return x;
}
递归:
int findFather(int x)
{
if (x == father[x])
return x;
else
return findFather(father[x]);
}
- 合并
合并就是帮其中一个集合的根节点的父亲指向另一个集合的根节点。
- 首先要确保两个集合不属于同一个集合,调用上面的findFather函数,对两个元素a、b分别查找根节点,然后再判断根节点是否相同
- 利用上步找到的根节点faA和faB,因此只需要把其中一个父亲结点指向另一个结点,father[faA] = faB或者father[faB] = faA都行
void Union(int a, int b)
{
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB)
father[faA] = faB;
}
路径压缩
如果元素很多并且连成一条链,那么查找函数效率就会很低,但是可以这样实现路径压缩:
1 1
| ——> / | \
2 2 3 4
|
3
|
4
将当前查询结点路径上所有结点的父亲都指向根结点,将复杂度优化到O(1)。
int findFather(int x)
{
int a = x; //先保存x的值
while (x != father[x]) //x指向父结点
x = father[x];
while (a != father[a])
{
int z = a; //z指向a
a = father[a]; //a往上移
father[z] = x; //将z的父结点变成根结点
}
return x;
}
递归写法:
int findFather(int x)
{
if (x == father[x])
return x;
else
{
int F = findFather(father[x]);
father[x] = F;
return F;
}
}
例题1:
有关联的好朋友都合并到同一个组中
#include <iostream>
#include <cstdlib>
using namespace std;
int father[101];
void init(int n);
void Union(int a, int b);
int findFather(int a);
bool isRoot(int a);
int main()
{
int n, m;
cin >> n >> m;
init(n);
for (int i = 0; i < m; i++)
{
int friA, friB;
cin >> friA >> friB;
Union(friA, friB); //合并到同一个组中
}
int cnt = 0;
for (int i = 1; i <= n; i++)
if (isRoot(i)) //最后数有几个根即可
cnt++;
cout << cnt << endl;
system("pause");
return 0;
}
void init(int n)
{
for (int i = 1; i <= n; i++)
father[i] = i;
}
int findFather(int a)
{
while (father[a] != a)
a = father[a];
return a;
}
void Union(int a, int b)
{
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB)
father[faA] = faB;
}
bool isRoot(int a)
{
return findFather(a) == a;
}