普通并查集(找领导)
并查集被很多OIer认为是最简洁而优雅的数据结构之一,主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:
- 合并(Union):把两个不相交的集合合并为一个集合。
- 查询(Find):查询两个元素是否在同一个集合中。
并查集的重要思想在于,用集合中的一个元素代表集合。
并查集初始状态:
通过一系列转化:
最终状态:
let fa = [];let rank = [];function init(n) {for (let i = 1; i <= n; i++) {fa[i] = i;rank[i] = i;}}function find(x) {return fa[x] === x ? x : (fa[x] = find(fa[x]));}function merge(i, j) {let x = find(i), y = find(j);if (rank[x] <= rank[y]) {fa[x] = y;} else {fa[y] = x;}if (rank[x] === rank[y] && x != y) {rank[y]++;}}init(6);merge(2, 3);merge(1, 3);merge(3, 4);merge(5, 6);let x = 6;let y = 3;console.log(find(x) === find(y) ? "Yes" : "No");
这个根据调试得到的所有merge后的fa数组对应元素:
- 查询:递归查询该元素的祖元素
- 合并:把一个集合簇的祖元素接到另一个集合簇的祖元素上
- rank:判断是x集合接到y上还是y接到x上
种类并查集(敌人的敌人是朋友)
开一个两倍大小的并查集。例如,假如我们要维护4个元素的并查集,我们改为开8个单位的。
我们merge(1, 2+n), merge(1+n, 2);。这里n就等于4,但我写成n这样更清晰。对于1个编号为i的元素,i+n是它的敌人。所以这里的意思就是:1是2的敌人,2是1的敌人。
现在假如我们又知道2和4是敌人,我们merge(2, 4+n), merge(2+n, 4);:
敌人的敌人就是朋友,2和4是敌人,2和1也是敌人,所以这里,1和4通过2+n这个元素间接地连接起来了。这就是种类并查集工作的原理。
捕食问题:可以用一个三倍大小的并查集进行维护。
- rank:判断是x集合接到y上还是y接到x上
