Skip to content

Commit 625a97e

Browse files
authored
Merge pull request kodecocodes#445 from antonio081014/UnionFindMoreImplementation
Add variety implementation with complexity analysis.
2 parents ca28850 + 83f7a32 commit 625a97e

9 files changed

+330
-149
lines changed

Union-Find/README.markdown

+47-16
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ The most common application of this data structure is keeping track of the conne
2323

2424
## Implementation
2525

26-
Union-Find can be implemented in many ways but we'll look at the most efficient.
26+
Union-Find can be implemented in many ways but we'll look at an efficient and easy to understand implementation: Weighted Quick Union.
27+
> __PS: Variety implementation of Union-Find has been included in playground.__
2728
2829
```swift
2930
public struct UnionFind<T: Hashable> {
@@ -141,24 +142,24 @@ public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool
141142

142143
Since this calls `setOf()` it also optimizes the tree.
143144

144-
## Union
145+
## Union (Weighted)
145146

146147
The final operation is **Union**, which combines two sets into one larger set.
147148

148149
```swift
149-
public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) {
150-
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { // 1
151-
if firstSet != secondSet { // 2
152-
if size[firstSet] < size[secondSet] { // 3
153-
parent[firstSet] = secondSet // 4
154-
size[secondSet] += size[firstSet] // 5
155-
} else {
156-
parent[secondSet] = firstSet
157-
size[firstSet] += size[secondSet]
158-
}
150+
public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) {
151+
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { // 1
152+
if firstSet != secondSet { // 2
153+
if size[firstSet] < size[secondSet] { // 3
154+
parent[firstSet] = secondSet // 4
155+
size[secondSet] += size[firstSet] // 5
156+
} else {
157+
parent[secondSet] = firstSet
158+
size[firstSet] += size[secondSet]
159+
}
160+
}
161+
}
159162
}
160-
}
161-
}
162163
```
163164

164165
Here is how it works:
@@ -167,7 +168,7 @@ Here is how it works:
167168

168169
2. Check that the sets are not equal because if they are it makes no sense to union them.
169170

170-
3. This is where the size optimization comes in. We want to keep the trees as shallow as possible so we always attach the smaller tree to the root of the larger tree. To determine which is the smaller tree we compare trees by their sizes.
171+
3. This is where the size optimization comes in (Weighting). We want to keep the trees as shallow as possible so we always attach the smaller tree to the root of the larger tree. To determine which is the smaller tree we compare trees by their sizes.
171172

172173
4. Here we attach the smaller tree to the root of the larger tree.
173174

@@ -185,10 +186,40 @@ Note that, because we call `setOf()` at the start of the method, the larger tree
185186

186187
Union with optimizations also takes almost **O(1)** time.
187188

189+
## Path Compression
190+
```swift
191+
private mutating func setByIndex(_ index: Int) -> Int {
192+
if index != parent[index] {
193+
// Updating parent index while looking up the index of parent.
194+
parent[index] = setByIndex(parent[index])
195+
}
196+
return parent[index]
197+
}
198+
```
199+
Path Compression helps keep trees very flat, thus find operation could take __ALMOST__ in __O(1)__
200+
201+
## Complexity Summary of Variety Implementation
202+
203+
##### To process N objects
204+
| Data Structure | Union | Find |
205+
|---|---|---|
206+
|Quick Find|N|1|
207+
|Quick Union|Tree height|Tree height|
208+
|Weighted Quick Union|lgN|lgN|
209+
|Weighted Quick Union + Path Compression| very close, but not O(1)| very close, but not O(1) |
210+
211+
##### To process M union commands on N objects
212+
| Algorithm | Worst-case time|
213+
|---|---|
214+
|Quick Find| M N |
215+
|Quick Union| M N |
216+
|Weighted Quick Union| N + M lgN |
217+
|Weighted Quick Union + Path Compression| (M + N) lgN |
218+
188219
## See also
189220

190221
See the playground for more examples of how to use this handy data structure.
191222

192223
[Union-Find at Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
193224

194-
*Written for Swift Algorithm Club by [Artur Antonov](https://github.com/goingreen)*
225+
*Written for Swift Algorithm Club by [Artur Antonov](https://github.com/goingreen)*, *modified by [Yi Ding](https://github.com/antonio081014).*

Union-Find/UnionFind.playground/Contents.swift

+14-68
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,19 @@
11
//: Playground - noun: a place where people can play
22

3-
public struct UnionFind<T: Hashable> {
4-
private var index = [T: Int]()
5-
private var parent = [Int]()
6-
private var size = [Int]()
7-
8-
public mutating func addSetWith(_ element: T) {
9-
index[element] = parent.count
10-
parent.append(parent.count)
11-
size.append(1)
12-
}
13-
14-
private mutating func setByIndex(_ index: Int) -> Int {
15-
if parent[index] == index {
16-
return index
17-
} else {
18-
parent[index] = setByIndex(parent[index])
19-
return parent[index]
20-
}
21-
}
22-
23-
public mutating func setOf(_ element: T) -> Int? {
24-
if let indexOfElement = index[element] {
25-
return setByIndex(indexOfElement)
26-
} else {
27-
return nil
28-
}
29-
}
30-
31-
public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) {
32-
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
33-
if firstSet != secondSet {
34-
if size[firstSet] < size[secondSet] {
35-
parent[firstSet] = secondSet
36-
size[secondSet] += size[firstSet]
37-
} else {
38-
parent[secondSet] = firstSet
39-
size[firstSet] += size[secondSet]
40-
}
41-
}
42-
}
43-
}
44-
45-
public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool {
46-
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
47-
return firstSet == secondSet
48-
} else {
49-
return false
50-
}
51-
}
52-
}
53-
54-
55-
56-
57-
var dsu = UnionFind<Int>()
3+
var dsu = UnionFindQuickUnion<Int>()
584

595
for i in 1...10 {
60-
dsu.addSetWith(i)
6+
dsu.addSetWith(i)
617
}
628
// now our dsu contains 10 independent sets
639

6410
// let's divide our numbers into two sets by divisibility by 2
6511
for i in 3...10 {
66-
if i % 2 == 0 {
67-
dsu.unionSetsContaining(2, and: i)
68-
} else {
69-
dsu.unionSetsContaining(1, and: i)
70-
}
12+
if i % 2 == 0 {
13+
dsu.unionSetsContaining(2, and: i)
14+
} else {
15+
dsu.unionSetsContaining(1, and: i)
16+
}
7117
}
7218

7319
// check our division
@@ -88,20 +34,20 @@ print(dsu.inSameSet(3, and: 6))
8834

8935

9036

91-
var dsuForStrings = UnionFind<String>()
37+
var dsuForStrings = UnionFindQuickUnion<String>()
9238
let words = ["all", "border", "boy", "afternoon", "amazing", "awesome", "best"]
9339

9440
dsuForStrings.addSetWith("a")
9541
dsuForStrings.addSetWith("b")
9642

9743
// In that example we divide strings by its first letter
9844
for word in words {
99-
dsuForStrings.addSetWith(word)
100-
if word.hasPrefix("a") {
101-
dsuForStrings.unionSetsContaining("a", and: word)
102-
} else if word.hasPrefix("b") {
103-
dsuForStrings.unionSetsContaining("b", and: word)
104-
}
45+
dsuForStrings.addSetWith(word)
46+
if word.hasPrefix("a") {
47+
dsuForStrings.unionSetsContaining("a", and: word)
48+
} else if word.hasPrefix("b") {
49+
dsuForStrings.unionSetsContaining("b", and: word)
50+
}
10551
}
10652

10753
print(dsuForStrings.inSameSet("a", and: "all"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Foundation
2+
3+
/// Quick-find algorithm may take ~MN steps
4+
/// to process M union commands on N objects
5+
public struct UnionFindQuickFind<T: Hashable> {
6+
private var index = [T: Int]()
7+
private var parent = [Int]()
8+
private var size = [Int]()
9+
10+
public init() {}
11+
12+
public mutating func addSetWith(_ element: T) {
13+
index[element] = parent.count
14+
parent.append(parent.count)
15+
size.append(1)
16+
}
17+
18+
private mutating func setByIndex(_ index: Int) -> Int {
19+
return parent[index]
20+
}
21+
22+
public mutating func setOf(_ element: T) -> Int? {
23+
if let indexOfElement = index[element] {
24+
return setByIndex(indexOfElement)
25+
} else {
26+
return nil
27+
}
28+
}
29+
30+
public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) {
31+
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
32+
if firstSet != secondSet {
33+
for index in 0..<parent.count {
34+
if parent[index] == firstSet {
35+
parent[index] = secondSet
36+
}
37+
}
38+
size[secondSet] += size[firstSet]
39+
}
40+
}
41+
}
42+
43+
public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool {
44+
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
45+
return firstSet == secondSet
46+
} else {
47+
return false
48+
}
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Foundation
2+
3+
/// Quick-Union algorithm may take ~MN steps
4+
/// to process M union commands on N objects
5+
public struct UnionFindQuickUnion<T: Hashable> {
6+
private var index = [T: Int]()
7+
private var parent = [Int]()
8+
private var size = [Int]()
9+
10+
public init() {}
11+
12+
public mutating func addSetWith(_ element: T) {
13+
index[element] = parent.count
14+
parent.append(parent.count)
15+
size.append(1)
16+
}
17+
18+
private mutating func setByIndex(_ index: Int) -> Int {
19+
if parent[index] == index {
20+
return index
21+
} else {
22+
parent[index] = setByIndex(parent[index])
23+
return parent[index]
24+
}
25+
}
26+
27+
public mutating func setOf(_ element: T) -> Int? {
28+
if let indexOfElement = index[element] {
29+
return setByIndex(indexOfElement)
30+
} else {
31+
return nil
32+
}
33+
}
34+
35+
public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) {
36+
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
37+
if firstSet != secondSet {
38+
parent[firstSet] = secondSet
39+
size[secondSet] += size[firstSet]
40+
}
41+
}
42+
}
43+
44+
public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool {
45+
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
46+
return firstSet == secondSet
47+
} else {
48+
return false
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Foundation
2+
3+
/// Quick-find algorithm may take ~MN steps
4+
/// to process M union commands on N objects
5+
public struct UnionFindWeightedQuickFind<T: Hashable> {
6+
private var index = [T: Int]()
7+
private var parent = [Int]()
8+
private var size = [Int]()
9+
10+
public init() {}
11+
12+
public mutating func addSetWith(_ element: T) {
13+
index[element] = parent.count
14+
parent.append(parent.count)
15+
size.append(1)
16+
}
17+
18+
private mutating func setByIndex(_ index: Int) -> Int {
19+
return parent[index]
20+
}
21+
22+
public mutating func setOf(_ element: T) -> Int? {
23+
if let indexOfElement = index[element] {
24+
return setByIndex(indexOfElement)
25+
} else {
26+
return nil
27+
}
28+
}
29+
30+
public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) {
31+
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
32+
if firstSet != secondSet {
33+
if size[firstSet] < size[secondSet] {
34+
for index in 0..<parent.count {
35+
if parent[index] == firstSet {
36+
parent[index] = secondSet
37+
}
38+
}
39+
size[secondSet] += size[firstSet]
40+
} else {
41+
for index in 0..<parent.count {
42+
if parent[index] == secondSet {
43+
parent[index] = firstSet
44+
}
45+
}
46+
size[firstSet] += size[secondSet]
47+
}
48+
}
49+
}
50+
}
51+
52+
public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool {
53+
if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) {
54+
return firstSet == secondSet
55+
} else {
56+
return false
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)