迷之Question:我是不是好久没更了???(
( 其实本人心里毫不内疚的
讲一下并查集~
并查集
并查集,顾名思义啦,就是一种动态维护几个不重叠的集合,做合并与查询的数据结构。
基本操作:
- Get,查询一个元素属于哪一个集合;(也叫find
- Merge,合并两个集合。 (也叫union
实现
如何实现并查集这种数据结构呐?
这边两种方法供选择噢:
- 维护一个数组f,用f[x]保存元素x所在的集合的代表;
- 用一个树形结构存储每个集合,整个并查集即是一个树林。我们维护一个数组fa记录这个森林,用fa[x]保存父节点,令树根的fa值为她自己;这样在合并的时候只需要连接两个root。
开扯了…
路径压缩
不知道有没有人注意到第一种思路的查询效率很高,那我们不妨考虑把两种思路结合。对于第二种思路,我们只关心每个集合对应的“树形结构”的根节点,而非这棵树的具体形态。
所以说,这棵树:
和这棵树:
是等价的。
(不要问我为什么这么丑,因为是没有任何艺术细胞的我画的qaq
所以呐,我们可以在每次执行get操作的同时,把访问过的每个节点都指向树根,也就是把第一棵树变成第二棵~那这个方法就是传说中的路径压缩啦!
ps采用路径压缩优化的并查集,每次get操作的均摊复杂度为O(logN);
按秩合并
现在来唠一唠按秩合并。
按秩合并的基本思想就是让包含结点少的树的根指向包含结点多的树的根,也就是把“小的结构”合并到“大的结构”里去,并且只增加“小的结构”的查询代价。
ps采用按秩合并优化的并查集,每次get操作的均摊复杂度也是O(logN)哦;
顺带一提,同时采用路径压缩和按秩合并优化的并查集,每次get操作的均摊复杂度可以降低到
O
(
α
(
N
)
)
O(α(N))
O(α(N)),至于
α
(
N
)
α(N)
α(N)是“反阿克曼函数”,自己baidu吧(咕咕咕
一个小栗子
模板题:洛谷p3367
题解:
按照上文说的,维护一个数组get(查询),要用路径压缩优化的哦;
部分代码:
- 维护part:
int get(int x) {
if (x==fa[x]) return x;
return fa[x]=get(fa[x]); //这块儿是模板哈哈哈哈哈
}
- 部分主程序part:
for (int i=1;i<=n;i++)
fa[i]=i; //初始化
for (int i=1;i<=m;i++)
{
cin>>zi>>xi>>yi;
if (zi==1)
fa[get(xi)]=get(yi); //调用数组
else
{
if (get(xi)==get(yi))
cout<<"Y"<<endl;
else
cout<<"N"<<endl;
}
}
模板
大家都看到了get的模板mia(
都搁这儿啊:
get:
int get(int x) {
if (x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
merge:
void merge(int x,int y) {
fa[get(x)]=get(y);
}
初始化:(虽然我也不知道这个为什么会成为模板
for (int i=1;i<=n;i++)
fa[i]=i;
结尾(
值得一提的还有“扩展域”与“边带权”的并查集,然鹅我并没有学废/kk,所以等我学废了再来补吧(手动翻译:咕咕咕
好啦就酱,拜拜