0%

并查集

并查集主要用来解决动态连通性问题。
连通性问题:
LC200 Number of Islands
LC305 Number of Islands II
LC547 Friend Circles
LC130 Surrounded Regions

LC737 句子相似性II
LC721, 账户合并

实现并查集

Weighted Quick-Union with Path Compression

建议参考:Algorithm 4th edition, Union-Find

定义:

  • 一个节点i的父节点为tree[i]
  • 一个节点i所在的树的根节点j为父节点为自己的祖先节点,即tree[j] == j

初始化:

  • 所有的节点i的父节点为其自己,即初始化时赋值tree[i] = i
  • 因此,以所有节点i为根节点的集合(树)的初始化为1

find()的实现思路:

  • 两个节点在一个集合(树)中,当且仅当他们的集合(树)的根节点是相同的。
  • Path compression: 每次在找到一个节点所在集合的根节点的过程中,将从根节点到该节点路径上的所有节点的父节点都设置为根节点。
    union() / connect()的实现思路:
  • 要将两个节点合并,就是将他们所在的集合(树)合并。因此我们可以把其中一个集合(树)的根节点的父节点设置为另一个集合(树)的根节点。
  • Weighted Quick-Union: 我们记录以每一个节点为根节点的树的大小。我们总是将含有元素较少的集合(树)的根节点的父节点改为含有较多元素的集合(树)的根节点。

连通性问题

岛屿数量

LC200, Number of Islands, LC305, Number of Islands II

其中第一题LC200, Number of Islands不是动态的连通性问题,因此也可以用DFS来统计深度优先森林中树的个数或 用BFS来遍历。

思路1 并查集:

  • 将坐标[i][j]转换成唯一对应的index = i * numCols + j,作为并查集的index
  • 初始化一个同样大小的并查集和一个计数器count=0
  • 遍历矩阵,每次遇到1的时候
    • 增加计数器++count
    • 尝试合并这个元素[i][j]和他上方的元素[i - 1][j]以及他左边的元素[i][j - 1]
    • 如果这个元素和他上方的元素以及他左边的元素不在一个set里,那么减少计数器--count。因为:
      • 如果这个元素和其中一个元素连通了,没有增加岛屿的数量。
      • 如果这个元素和两个都成功连通了,那么这个元素就将矩阵里原来的两个岛接在了一起。那么岛屿数量会-1。(++count, --count, --count)
  • 返回计数器的值

思路2: BFS

遍历所有节点,每当遇到1的节点时,计数器+1。之后从该节点开始进行BFS。BFS过程中,把所有遇到的节点都置0

注意:在BFS时,要在第一次遇到一个节点时就将该节点置'0',否则将会导致重复遍历。

1
2
3
4
5
6
7
8
9
10
11
                            duplicate
+
|
v
+-----+ +-----+ ++----+
|1|1| | |1|0| | |1|0| |
+-----+ +-----+ +-----+
|1|X|1| +--> |1|0|1| +--> |0|0|1|
+-----+ +-----+ +-----+
| |1| | | |1| | | |1| |
+-----+ +-----+ +-----+

'X'的位置开始进行BFS,按照“上左下右”的顺序将'X'周围的四个LAND放入BFS队列中而不清零。之后从队列中取出队列头(即'X'上面的'1'),将该队列头清零后,把其左边的'1'放入队列。之后再取出队列头(即'X'右边的'1')。注意此时在便利该队列头周围的节点时,会重复地将左上角的'1'再次放入队列。


朋友圈

LC547 Friend Circles
同上两题。这个题可以利用无向图邻接矩阵的对称性,不需要遍历矩阵所有部分。只需要遍历右上角即可。

在并查集里统计不交集的个数。开始时不交集的个数等于节点的个数。每次成功合并两个不交集,都会使得不交集的个数减1。


账户合并


LC721, 账户合并

  • 按列表中的元素个数,建立一个相同大小的并查集。列表中元素的索引,即是其在并查集中的索引。
  • 建立一个由【邮箱】到【索引】的unordered_map : mail_index_map
  • 遍历所有的邮箱:
    • 如果在mail_index_map中没有找到对应的【索引】,则建立对应关系。
    • 如果在mail_index_map中找到了对应的【索引】,则出现了重复的邮箱。那么这个【邮箱】所对应的索引,应该和原来的【索引】合并。在并查集中connect对应的【索引】
  • 建立一个由【索引】到【邮箱集合】(set)的unordered_map
  • 遍历所有的【索引】,在并查集中找到他们的root。将该【索引】下的所有邮箱放入root对应的【邮箱集合】中。
  • 遍历由【索引】到【邮箱集合】的map,生成结果。

被围绕的区域

LC130, Surrounded Regions

这个题更适合用DFS,而不是并查集。DFS不需要遍历所有的元素,只生成从边界上的'O'出发的深度优先森林即可。
并查集的思路:

- 设置哨兵元素`edgeGuard`
- 按从上到下、从左到右的顺序遍历矩阵每一个元素:
    - 如果是边界上的元素,那么把他和`edgeGurad`合并
    - 和左边元素、上边元素合并。
- 寻找所有O的root,不是`edgeGuard`的就改成`X`

句子相似性

LC737 句子相似性II

并查集的简单应用。需要注意的:

  • 句子中会有下面相似单词中没有的单词。
  • 如果出现没有的单词,也不可以马上认为句子不相似。因为如果这两个没有的单词可能是完全相同的。

动态连通性

LC305, Number of Islands II

动态连通性问题,同上题,维护一个并查集即可。 需要注意的问题是:测试用例中有positions中有重复的坐标,需要判断并跳过。