目录
并查集 (Disjoint-set)
Step 1:
Step2:
Step 3:
Step 4:
避免最坏情况:
继续优化:
I - Namesolo 拜师
一:维护生成树
二:按秩合并
证明:
三:路径压缩
并查集 (Disjoint-set)
每个集合用一棵“有根树”表示 定义数组 set[1…n] set[i] = i, 则i表示本集合,且是集合对应树的根 set[i] = j,j<>i, 则 j 是 i 的父节点.
int findSet(int x)
{
if (x == set[x])
return x;
else
return findSet(set[x]);
}
void unionSet(int x, int y)
{
int fx = findSet(x);
int fy = findSet(y);
set[fy] = fx;
}
Step 1: nobody is anybody friend. We have 5 trees and each tree has a single element, which is the root and the representative of that tree.
i | 1 | 2 | 3 | 4 | 5 |
set[i] | 1 | 2 | 3 | 4 | 5 |
Step2: 1 and 2 are friends, unionSet(1, 2) Then we have 4 trees one tree contain 2 elements and have the root 1. The other trees have a single element.
i | 1 | 2 | 3 | 4 | 5 |
set[i] | 1 | 1 | 3 | 4 | 5 |
Step 3: 5 and 4 are friends, unionSet(5, 4) Now we have 3 trees, 2 trees with 2 elements and one tree with one element.
i | 1 | 2 | 3 | 4 | 5 |
set[i] | 1 | 1 | 3 | 5 | 5 |
Step 4: 5 and 1 are friends, unionSet(1, 5) Now we have 2 trees, one tree has 4 elements and the other one has only one element.
i | 1 | 2 | 3 | 4 | 5 |
set[i] | 1 | 1 | 3 | 5 | 1 |
算法复杂度: findSet(x) 最坏O(n) unionSet(x, y) 最坏O(n)
最坏:
避免最坏情况:
启发式合并 方法:将深度小的树合并到深度大的树 实现:假设两棵树的深度分别为h1和h2, 则合并后的树的高度h是: max(h1,h2), if h1<>h2. h1+1, if h1=h2. 效果:任意顺序的合并操作以后,包含k个节点的树的最大高度不超过lgk
void unionSet(int x, int y)
{
fx = findset(x);
fy = findset(y);
if(rank[fx] > rank[fy])
{
set[fy] = fx;
}
else
{
set[fx] = fy;
if(rank[fx] == rank[fy])
rank[fy]++;
}
}
继续优化:
路径压缩(Path Compression) 思想:每次查找的时候,把经过路径上的点的父亲都设为根 步骤: 第一步,找到根结点 第二步,修改查找路径上的所有节点,将它们都指向根结点 可以证明m次操作的总时间复杂度为k*O(m),k是一个接近1的常数,即几乎是线性的。 使用路径压缩的并查集算法不需要再使用启发式合并。
int findSet(int x)
{
if (x == set[x])
return x;
else
return set[x] = findSet(set[x]);
}
I - Namesolo 拜师
给定
n
个点的图,初始没有边,要求支持以下操作:
•
加一条边
(
u
,
v
)
。
•
询问
u
,
v
是否连通。
并查集裸题,需要注意使用快读输入,以及取模输出。
一:维护生成树
只需要维护联通性,那么只要维护一棵生成树即可。
•
每次询问是否连通,只需要查是否在同一棵树内,一直向上询问直到得到树根是否相同即可。
•
加边前先判断是否连通,若不连通,将其中一个端点设为另 一端点的双亲节点。
![](https://i-blog.csdnimg.cn/blog_migrate/db650a5a564ea9f71ab10835579fb068.png)
期望复杂度 O(m log n),最坏复杂度 O(nm)。
二:按秩合并
设节点
x
的秩
rank
x
表示以
x
为根子树树高(最深叶子到
x
的 距离),为减少树高,将秩小的节点的双亲节点设为秩大的节点。
•
两棵树树高不同时,最终树高等于树高较大的树的树高。
•
树高相同时,最终树高等于树高较大的树的树高
+1
。
设以
x
为根的子树节点个数为
n
,则其秩不超过
⌊
log
2
n
⌋
证明:
n
= 1
时,
rank
x
= 0 =
⌊
log
2
1
⌋
假设
n
= 1
,
2
, ...,
k
时均满足秩不超过
⌊
log
2
n
⌋
n
=
k
+ 1
时,设
u
,
v
所在的树大小分别为
a
,
b
,其中
a
≤
b
若
rank
u !
=
rank
v
,则
newrank
=
max
(
rank
u
,
rank
v
)
≤
max
(
⌊
log
2
a
⌋
,
⌊
log
2
b
⌋
)
≤ ⌊
log
2
n
⌋
若
rank
u
=
rank
v
,则
newrank
=
rank
u
+ 1
≤ ⌊
log
2
a
⌋
+ 1 =
⌊
log
2
2
a
⌋ ≤ ⌊
log
2
n
⌋
期望复杂度
O
(
m
log
n
)
,最坏复杂度
O
(
m
log
n
)
。
三:路径压缩
只需要寻找树根,重复询问同一个节点,向上询问的路径重复。 向上询问到树根形成一条路径,每次询问这条路径上的节点,都 一定会到达树根。 直接将父亲节点修改为树根,减少重复路径访问。
期望复杂度 O(mα(n)),最坏复杂度 O(m log1+ m/n n)。