本文是对 Swift Algorithm Club 翻译的一篇文章。
Swift Algorithm Club是 raywenderlich.com网站出品的用Swift实现算法和数据结构的开源项目,目前在GitHub上有18000+⭐️,我初略统计了一下,大概有一百左右个的算法和数据结构,基本上常见的都包含了,是iOSer学习算法和数据结构不错的资源。 ?andyRon/swift-algorithm-club-cn是我对Swift Algorithm Club,边学习边翻译的项目。由于能力有限,如发现错误或翻译不妥,请指正,欢迎pull request。也欢迎有兴趣、有时间的小伙伴一起参与翻译和学习?。当然也欢迎加⭐️,?????。 本文的翻译原文和代码可以查看?swift-algorithm-club-cn/Union-Find GitHub地址: https://github.com/andyRon/swift-algorithm-club-cn/tree/master/Union-Find 并查集(Union-Find) 并查集是一种数据结构,可以跟踪一组元素,它们分布在几个不相交(非重叠)子集合中。它也被称为不相交集数据结构。 这是什么意思呢?例如,并查集数据结构可以跟踪以下集合:
[ a, b, f, k ]
[ e ]
[ g, d, c ]
[ i, j ]
这些集合是
不相交的,因为它们没有共同的成员。 并查集支持三个基本操作:
PS:并查集 的多个实现已包含在playground .
public struct UnionFind {
private var index = [T: Int]()
private var parent = [Int]()
private var size = [Int]()
}
我们的并查集数据结构实际上是一个森林,其中每个子集由 树 表示。 基于我们的目的,我们只需要跟踪每个树节点的父节点,而不是子节点。为此,我们使用数组parent,那么parent[i]是节点i的父节点索引。 示例:如果parent看起来像这样,
parent [ 1, 1, 1, 0, 2, 0, 6, 6, 6 ]
i 0 1 2 3 4 5 6 7 8
然后树结构看起来像:
1 6
/ \ / \
0 2 7 8
/ \ /
3 5 4
这片森林中有两棵树,每棵树对应一组元素。(注意:由于ASCII的限制,树在这里显示为二叉树,但情况不一定如此。) 我们为每个子集提供唯一的编号以识别它。该数字是该子集树的根节点的索引。在示例中,节点1是第一棵树的根节点,6是第二棵树的根节点。 所以在这个例子中我们有两个子集,第一个带有标签1,第二个带有标签6。Find操作实际上返回了set的标签,而不是其内容。 请注意,根节点的parent[]指向自身。所以parent[1] = 1和 parent [6] = 6。这就是我们如何判断那些是根节点的方法。
public mutating func addSetWith(_ element: T) {
index[element] = parent.count // 1
parent.append(parent.count) // 2
size.append(1) // 3
}
添加新元素时,实际上会添加一个仅包含该元素的新子集。
public mutating func setOf(_ element: T) -> Int? {
if let indexOfElement = index[element] {
return setByIndex(indexOfElement)
} else {
return nil
}
}
这会在index字典中查找元素的索引,然后使用辅助方法来查找此元素所属的集合:
private mutating func setByIndex(_ index: Int) -> Int {
if parent[index] == index { // 1
return index
} else {
parent[index] = setByIndex(parent[index]) // 2
return parent[index] // 3
}
}
因为我们正在处理树结构,所以这边使用的是递归方法。 回想一下,每个集合由树表示,并且根节点的索引用作标识集合的数字。我们将找到我们要搜索的元素所属的树的根节点,并返回其索引。
public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool {
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
return firstSet == secondSet
} else {
return false
}
}
这会调用setOf(),也会优化树。
public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) {
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { // 1
if firstSet != secondSet { // 2
if size[firstSet] // 3
parent[firstSet] = secondSet // 4
size[secondSet] += size[firstSet] // 5
} else {
parent[secondSet] = firstSet
size[firstSet] += size[secondSet]
}
}
}
}
下面是它的工作原理:
private mutating func setByIndex(_ index: Int) -> Int {
if index != parent[index] {
// Updating parent index while looking up the index of parent.
parent[index] = setByIndex(parent[index])
}
return parent[index]
}
路径压缩有助于保持树非常平坦,因此查找操作可能只需要__O(1)__ 。
Union | Find | |
Quick Find | N | 1 |
Quick Union | Tree height | Tree height |
Weighted Quick Union | lgN | lgN |
Weighted Quick Union + Path Compression | very close, but not O(1) | very close, but not O(1) |
Worst-case time | Algorithm |
Quick Find | M N |
Quick Union | M N |
Weighted Quick Union | N + M lgN |
Weighted Quick Union + Path Compression | (M + N) lgN |
推荐↓↓↓
长
按
关
注
?【16个技术公众号】都在这里!
涵盖:程序员大咖、源码共读、程序员共读、数据结构与算法、黑客技术和网络安全、大数据科技、编程前端、Java、Python、Web编程开发、Android、iOS开发、Linux、数据库研发、幽默程序员等。
万水千山总是情,点个 “ 在看” 行不行