1. 结构实现
并查集要实现三个方法:
int find(int x);
查找代表元素
bool isSameSet(int x, int y);
查看是否为同一集合
void _union(int x, int y);
合并集合
而并查集的结构,我们可以使用数组(顺序表)进行存储。数组下标i
表示每个节点,数组的值father[i]
对应着这个节点的代表节点。
对于father
数组的初始化,我们有如下代码:
1 2
| for (int i = 0; i < n; ++i) father[i] = i;
|
这样初始化,表示着每个节点一开始代表着自己。后期我们查找某个节点的代表节点的时候,只需依次跳转,直到father[i] == i
就表示找到代表节点了
接下来,我们依次来看前面三种方法的实现
1.1 find
1 2 3 4
| int find(int x) { if (x != father[x]) father[x] = find(father[x]); return father[x]; }
|
注意,这里在每次的递归中father[x] = find(x);
保证最后的扁平化,保证时间复杂度保持在常数级别。
1.2 isSameSet
1 2 3
| bool isSameSet(int x, int y) { return find(x) == find(y); }
|
查看代表元素是否相同,如果相同则表示在同一个集合中
1.3 _union
1 2 3 4 5 6 7
| void _union(int x, int y) { int fx = find(x), fy = find(y); if (fx != fy) { father[fx] = fy; } }
|
2. 具体案例
990. 等式方程的可满足性
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:”a==b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。
只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。
这题我们可以将相等看作两个节点联通,或者说,这两个节点属于同一个集合。而不等式可以看作这两个节点不属于同一个节点。
也就是说,我们使用并查集,先将等式的组合进行合并,再根据不等式中的字母元素,判断该元素是否同属一个节点。一旦出现矛盾,则返回false
解题代码如下:
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 26 27 28 29 30 31 32 33 34 35
| class Solution { public:
vector<int> father; bool isEqual(string const& s) { return s[1] == '='; } int find(int x) { if (x != father[x]) father[x] = find(father[x]); return father[x]; } void Union(int x, int y) { int fx = find(x), fy = find(y); if (fx != fy) { father[fx] = fy; } } bool isSameSet(int x, int y) { return find(x) == find(y); } bool equationsPossible(vector<string>& equations) { father.assign(26, 0); for (char i = 'a'; i <= 'z'; ++i) father[i-'a'] = i-'a'; for (auto s : equations) { if (isEqual(s)) Union(s[0]-'a', s.back()-'a'); } for (auto s : equations) { if (!isEqual(s)) if (isSameSet(s[0]-'a', s.back()-'a')) return false; } return true; } };
|