并查集
1.并查集是一种树型的数据结构,用于处理一些不相交集合的合并问题。
并查集的主要操作有
1.合并两个不相交集合(Union(int x,int y))
2.判断两个元素是否属于同一个集合(Find(int x))
3.路径压缩
定义一个数组father,用father[i]表示元素i的父亲结点.
初始化每个集合只有其本身(编号从1-N):
for (int i=1; i<=n; i++)
{
father[i]=i;
}
即对于节点
i
,它的组号也是
i。
合并操作(Union(int x,int y)),father[i]=j,表示j是i的父亲(即i,j属于同一集合)
查找操作(Find(int x))
查找两个元素是否属于同一集合(father[x]=x表示该元素没有合并过,自身为一个集合)
int Find(int x) //查找x所属集合
{
while (x!=Father[x])
{
x=Father[x];
}
return Father[x];
}
根据上面就可以写成合并操作了
void Union(int x,int y)
{
x=Find(x); //查找所属集合
y=Find(y);
if(x!=y) //不在同一个集合
{
Father[x]=y; //合并
}
}
又如:
上面所有元素最终形成三个集合。
合并操作的改进
最上面的Union的执行是相当任意的,它通过使得第二课棵树的子树而完成合并,对其进行简单的改进是借助任意的方法打破现有的关系,使得总让较小的树成为较大的树的子树,这种方法叫做按大小求并。
进行一次任意的并的结果(分别Union(5,6),Union(7,8),Union(5,7) 后Union(4,5)):
即总是size小的树作为子树和size大的树进行合并。这样就能够尽量的保持整棵树的平衡。
在初始情况下,每个组的大小都是1,因为只含有一个节点,所以我们可以使用额外的一个数组来维护每个组的大小,对该数组的初始化也很直观:
for (int i = 0; i < N; i++)
{
sz[i] = 1; // 初始情况下,每个组的大小都是1
}
而在进行合并的时候,会首先判断待合并的两棵树的大小,然后按照上面图中的思想进行合并,实现代码:
void Union(int x, int y)
{
int i = find(x);
int j = find(y);
if (i == j) return;
// 将小树作为大树的子树
if (sz[i] < sz[j])
{
Father[i] = j;
sz[j] += sz[i];
}
else
{
Father[j] = i;
sz[i] += sz[j];
}
}
形象化(上面的文字及此图来自 这里):
另一种实现方法为按高度求并,我们跟踪每棵树的高度而不是大小并执行那些Union使得浅点的树成为高点的树的子树,因为只有两棵相同深度的的树求并时树的高度才增加(此时树的高度增加1)。
void Union(int x,int y)
{
if(height[x]==height[y])
{
height[x]=height[x]+1;
Father[y]=x;
}
else if(height[x]<height[y])
{
Father[y]=x;
}
else
{
Father[x]=y;
}
}
路径压缩
通过 路径压缩使得结点离根更加的近,一个未采用路径压缩的例子:
路径压缩后:
这样采用路径压缩后使得Find更加的高效。
实现代码:
int Find(int x)
{
if(x!=Father[x])
{
Father[x]=Find(Father[x]); //递归找根进行更新
}
return Father[x];
}
点这里并查集应用