并查集
并查集主要用来解决元素分组的问题。它支持两种操作:
- 合并(Union):把两个不相交的集合合并成一个集合。
- 查询(Find):查询两个元素是否在同一个集合中。
并查集的核心思想是用集合中的一个元素代表整个集合。
并查集是一个树状的结构,要寻找集合中的代表元素,只需要向上一层一层访问父节点。
通过以上知识,可以先写出最简单版本的并查集代码。
初始化
1 | int fa[MAXN]; |
使用数组fa来存储每个元素的父节点。
查询
1 | int find(int x) |
使用递归一层一层访问父节点,直到根节点。通过判断两个元素的根节点是否一致,得出他们是否属于同一集合。
合并
1 | void merge(int i, int j) |
路径压缩
最简单的并查集效率是比较低的。merge(2,4)会使得树变成一条长链。随着链的增长,从底部找到跟节点将越来越慢。
最好能够使得每个元素到根节点的距离尽可能短,距离只有1。
为了实现路径压缩,只需要在查询的过程中,把沿途的每个节点的父节点都设为根节点。
1 | // 路径压缩 |
按秩合并
虽然经过了路径压缩这一优化,但并查集的结构仍然可能比较复杂。因为路径压缩只在查询时进行,而且只压缩一条路径。对于以下这种情况:
merge(7,8)有两种选择:把7的父节点设为8,或把8的父节点设为7。
显然后者好。要是把7的父节点设为8,会使树的深度增加1,每个节点寻找根节点的路径都会变长。虽然有路径压缩这一优化方案,但优化方案本身也是耗时的。如果把8的父节点设为7,就不会影响到原来的树结构。
所以,应该把简单的树往复杂的树上合并。
用一个数组depth记录每个根节点对应的树的深度。合并时比较两个根节点的depth,把depth小的往depth大的合并。
1 | void merge(int i, int j) |