1.并查集的概念
并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
2.并查集的应用
并查集主要用于解决一些分组问题,可以查询元素在哪个集合之中,并可以将不同的集合进行合并
3.实现原理
3.1
(1)初始化
将元素初始化为自身,即每个元素都是一个独立的集合
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
void init()
{
for (int i = 0; i < n; i++)
{
fa[i] = i;
}
}
(2)查询
查询元素的祖宗节点
int query(int x)
{
return fa[x] == x ? x : query(fa[x]); // 递归查询
}
(3)合并
void merge(int x, int y)
{
fa[query(x)] = query(y); // 把x的祖宗节点连到y上
}
以上为并查集的朴素写法
以上合并的时候效率比较低,举个例子:
如果我们要merge(3, 4),我们需要递归2次才能找到祖宗节点,以此类推,merge(3, 5)就需要递归3次,这样毫无疑问效率是很低的
那么我们可以把递归的次数压缩到一次吗?我们来改一下
int query(int x)
{
if(x == fa[x])
return x;
else
{
fa[x] = query(fa[x]); // 假设我们从这里把每一次的路径都改为祖宗节点
return fa[x];
}
}
这样我们在查询的时候就把路径压缩到1了
将路径压缩后,时间复杂度就会降得比较低了,为什么是比较低呢?因为如果我们每次合并的都是高一级的节点,那么下面的节点就永远没有机会连到祖宗节点,而由于我们合并集合的时候没有规定两个集合的优先级,就仍可能出现复杂的结构,举个例子:
我们上面写的合并操作是将右边集合的祖宗节点设置为左边集合的祖宗节点,即上图的第二种情况,这样做的后果就是整个集合的深度+1,空间更复杂了。于是,我们可以这样优化:将每个祖宗节点的深度记录下来,合并的时候就可以通过比较深度来确定优先级。
void init(int n)
{
for (int i = 0; i < n; ++i)
{
fa[i] = i;
d[i] = 0;
}
}
void merge(int x, int y)
{
int x = query(x), y = query(x); //先找到两个根节点
if (d[x] <= d[y])
fa[x] = y;
else
fa[y] = x;
if (d[x] == d[y] && x != y)
d[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1
}
不过这种方法有个缺点,就是不能反应真实的深度,只能反应出以前最大的一次深度而不能实时更新。
剩下的内容以后接着更新--------------