一.并查集
顾名思义并查集就是用来合并和查询集合的一种数据结构,通常可以达到接近O(1)的时间效率。并查集的实现是通过数组模拟树的结构来实现的。
并查集常用来记录和判断图的连通性问题。
基本原理:每个集合用一棵树来表示,树根(root)的编号就是整个集合的编号。每个节点存储它的父节点(father),p[x]表示x的父节点。
问题1;如何判断树根:if(p[x]==x)
问题2:如何求x的集合编号:while(p[x]!=x) x = p[x]
问题3: 如何合并两个集合:px是x的集合编号,py是y的集合编号,p[x] = y
路径压缩的优化:因为在寻找root时复杂度取决于树的高度。所以可以采用路径压缩。让孙子辈都指向祖宗,减少树的高度。
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
上面这个函数就是并查集的核心,实现了寻找祖宗和路径压缩的两个功能。
二.带权并查集
在每个树的边上添加一些额外的信息可以更好的处理需要解决的问题,在每条边中记录额外的信息的并查集就是带权并查集。
这个权值可以根据具体问题具体来定,但是这也带来了一些问题。
首先:每个节点都记录的是与根节点之间的权值,那么在Find的路径压缩过程中,权值也应该做相应的更新,因为在路径压缩之前,每个节点都是与其父节点链接着,那个Value自然也是与其父节点之间的权值。所以要进行权值更新!!!
再者:两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同。
所以我们的find函数变成了这个样子。
int find(int x)
{
if(p[x] != x)
{
int u = find(p[x]);
value[x] += value[p[x]];
p[x] = u;
}
return parent[x];
}
首先找到祖宗结点并且记录下来,然后函数递归到完开始退栈的时候,加上每一个结点的权值,最后路径压缩。
合并
int pa = find(a);
int pb = find(b);
if (pa != pb)
{
p[pa] = pb;
value[pa] = 新权值 //具体问题分析
}
例如上图:新权值 = s + value[y] - value[x]