并查集

并查集主要用于合并两个集合、查找两个元素是否属于同一集合。

主要想法是建立一个集合数字的哈希表,表中保存当前数字的祖宗节点,若祖宗节点相同,表示它们是一个集合。合并两个集合只需要将它们的祖宗节点设置为同一个即可(将其中一个集合的祖宗节点的祖宗节点设置为另一个集合的祖宗节点,这样保证集合下所有子节点都能合并为一个祖宗节点的节点)。

(1)朴素并查集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int p[N]; //存储每个点的祖宗节点

// 返回x的祖宗节点
int find(int x)
{
//if (p[x] != x) p[x] = find(p[x]); // 利用回溯压缩路径
//return p[x];
// 迭代压缩路径
int q = x, tmp;
while (p[q] != q) q = p[q]; // x的祖宗
while (x != q) { // 再从x开始查一遍,顺便把所有经过的节点祖宗更改。
tmp = p[x], p[x] = q, x = tmp;
}
return x;
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;

// 合并a和b所在的两个集合:将b的祖宗节点设置为a的祖宗节点的祖宗节点
p[find(a)] = find(b);

(2)维护size的并查集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}

// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);

(3)维护到祖宗节点距离的并查集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}

// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量