并查集是一种维护集合的数据结构,它的名字中“并”“查”“集”分别取自Union(合并)、Find(查找)、Set(集合)这3个单词。也就是说,并查集支持下面两个操作:
①合并:合并两个集合。
②查找:判断两个元素是否在一个集合。
那么并查集是用什么实现的呢?
其实就是用一个数组:
int father[N];
其中fahter[i]表示元素i的父亲结点,而父亲结点本身也是这个集合内的元素(1≤i≤N)。例如father[1]=2就表示元素1的父亲结点是元素2,以这种父系关系来表示元素所属的集合。另外,如果father[i]=i,则说明元素i是该集合的根结点,但对同一个集合来说只存在一个根结点,且将其作为所属集合的标识。
#include<iostream>
using namespace std;
const int max_union_find_set = 1001;
int father[max_union_find_set] = { 0 };
//初始化
void init(int father[]) {
for (int i = 1;i <= max_union_find_set;i++)
{
father[i] = i;
}
}
//查找(递归)
int find_father(int x) {
if (father[x] == x)
{
return x;
}
else
{
return find_father(father[x]);
}
}
//合并
void union_father(int a, int b) {
int father_a = find_father(a);
int father_b = find_father(b);
if (father_a != father_b)//如果不在一个集合,则合并
{
father[father_a] = father_b;
}
}
int main() {
system("pause");
return 0;
}
在合并的过程中,只对两个不同的集合进行合并,如果两个元素在相同的集合中,那么就不会对它们进行操作。这就保证了在同一个集合中一定不会产生环,即并查集产生的每一个集合都是一棵树。
上面讲解的并查集查找函数是没有经过优化的,在极端情况下效率较低。现在来考虑一种情况,即题目给出的元素数量很多并且形成一条链,那么这个查找函数的效率就会非常低。
下面给出解决方案:路径优化
这相当于把查找结点的路径上的所有结点的父亲都指向根结点
则查找的复杂度可以变为O(1)。
int find_father(int x) {
if (father[x] == x)
{
return x;
}
else
{
int root_val = find_father(father[x]);
father[x] = root_val;//将根结点赋值给father[x]
return root_val;//返回根结点
}
}