应用场景:
并查集可以非常快速的执行两个操作:
(1)将两个集合合并;
(2)询问两个元素是否在一个集合当中
基本原理:
用树的形式维护每个集合。每个集合的编号是根节点编号,每个点都存储了父节点是谁。当想求解某个点属于哪个集合的时候,一直找father找到树根。用这种方式快速找到元素属于哪个集合。每个节点存储他的父节点,p[x]表示x的父节点。
理解几个问题:
(1)如何判断树根?
如果是树根,有p[x] == x;如果不是树根,那么p[x] != x;
(2)如何求x的集合编号:
while (p[x] != x) x = p[x];只要x不是树根,就往上走。
(3)如何合并两个集合?
把其中一个集合的根作为另一个根的子节点,p[x] = y。
可以看到,第二步的时间复杂度比较高。如何优化呢? 用到路径压缩方法,直接将所有点,直接指向根节点。这是一个很好的加速,可以看成O(1)的时间复杂度了。并查集还有另外一个优化,按秩合并,但优化效果没有那么明显。写代码的时候不会写按秩合并的优化的。只会写这个路径压缩。
并查集的核心操作,只有两行:
int find(int x) // 返回x的祖宗节点,应用了路径压缩
{
if (p[x] != x) p[x] = find(p[x]); // 如果p[x]不等于x,则令p[x]等于x的祖宗节点
return p[x]; // 返回p[x],也就是返回了x的祖宗节点
}
这时给定了两个元素a和b,如果合并a、b所在集合的操作是:
p[find(a)] = find(b); // 将a的根节点变为b的根节点的子节点,“认爹”
如果判断a、b是否在同一个集合:
if (find(a) == find(b))