Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8ce99ee

Browse files
committedFeb 12, 2016
Add description with illustrations
1 parent 4419a0d commit 8ce99ee

File tree

5 files changed

+150
-2
lines changed

5 files changed

+150
-2
lines changed
 

‎Union-Find/Images/AfterFind.png

10.1 KB
Loading

‎Union-Find/Images/AfterUnion.png

25.7 KB
Loading

‎Union-Find/Images/BeforeFind.png

11.4 KB
Loading

‎Union-Find/Images/BeforeUnion.png

22.1 KB
Loading

‎Union-Find/README.markdown

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,151 @@
1-
# Union Find Data Structure
1+
# Union-Find data structure
22

3-
TODO
3+
Union-Find data structure (also known as disjoint-set data structure) is data structure that can keep track of a set of elements partitioned into a number of disjoint (non-overlapping) subsets. It supports three basic operations:
4+
1. Find(**A**): Determine which subset an element **A** is in
5+
2. Union(**A**, **B**): Join two subsets that contain **A** and **B** into a single subset
6+
3. AddSet(**A**): Add a new subset containing just that element **A**
7+
8+
The most common application of this data structure is keeping track of the connected components of an undirected graph. It is also used for implementing efficient version of Kruskal's algorithm to find the minimum spanning tree of a graph.
9+
10+
## Implementation
11+
12+
Union-Find can be implemented in many ways but we'll look at the most efficient.
13+
14+
Every Union-Find data structure is just value of type `UnionFind`
15+
16+
```swift
17+
public struct UnionFind<T: Hashable> {
18+
private var index = [T:Int]()
19+
private var parent = [Int]()
20+
private var size = [Int]()
21+
}
22+
```
23+
24+
Our Union-Find data structure is actually a forest where each subset represented by a [tree](../Tree/). For our purposes we only need to keep parent of each node. To do this we use array `parent` where `parent[i]` is index of parent of node with number **i**. In a that forest, the unique number of each subset is the index of value of root of that subset's tree.
25+
26+
So let's look at the implementation of basic operations:
27+
28+
### Add set
29+
30+
```swift
31+
public mutating func addSetWith(element: T) {
32+
index[element] = parent.count // 1
33+
parent.append(parent.count) //2
34+
size.append(1) // 3
35+
}
36+
```
37+
38+
1. We save index of new element in `index` dictionary because we need `parent` array only containing values in range 0..<parent.count.
39+
40+
2. Then we add that index to `parent` array. It's pointing itself because the tree that represent new set containing only one node which obviously is a root of that tree.
41+
42+
3. `size[i]` is a count of nodes in tree which root is node with number `i` We'll be using that in Union method.
43+
44+
45+
### Find
46+
47+
```swift
48+
private mutating func setByIndex(index: Int) -> Int {
49+
if parent[index] == index { // 1
50+
return index
51+
} else {
52+
parent[index] = setByIndex(parent[index]) // 2
53+
return parent[index] // 3
54+
}
55+
}
56+
57+
public mutating func setOf(element: T) -> Int? {
58+
if let indexOfElement = index[element] {
59+
return setByIndex(indexOfElement)
60+
} else {
61+
return nil
62+
}
63+
}
64+
```
65+
66+
`setOf(element: T)` is a helper method to get index corresponding to `element` and if it exists we return value of actual method `setByIndex(index: Int)`
67+
68+
1. First, we check if current index represent a node that is root. That means we find number that represent the set of element we search for.
69+
70+
2. Otherwise we recursively call our method on parent of current node. And then we do **very important thing**: we cache index of root node, so when we call this method again it will executed faster because of cached indexes. Without that optimization method's complexity is **O(n)** but now in combination with the size optimization (we'll look at that in Union section) it is almost **O(1)**.
71+
72+
3. We return our cached root as result.
73+
74+
Here's illustration of what I mean
75+
76+
Before first call `setOf(4)`:
77+
78+
![BeforeFind](Images/BeforeFind.png)
79+
80+
After:
81+
82+
![AfterFind](Images/AfterFind.png)
83+
84+
Indexes of elements are marked in red.
85+
86+
87+
### Union
88+
89+
```swift
90+
public mutating func unionSetsContaining(firstElement: T, and secondElement: T) {
91+
if let firstSet = setOf(firstElement), secondSet = setOf(secondElement) { // 1
92+
if firstSet != secondSet { // 2
93+
if size[firstSet] < size[secondSet] { // 3
94+
parent[firstSet] = secondSet // 4
95+
size[secondSet] += size[firstSet] // 5
96+
} else {
97+
parent[secondSet] = firstSet
98+
size[firstSet] += size[secondSet]
99+
}
100+
}
101+
}
102+
}
103+
```
104+
105+
1. We find sets of each element.
106+
107+
2. Check that sets are not equal because if they are it makes no sense to union them.
108+
109+
3. This is where our size optimization comes in. We want to keep trees as small as possible so we always attach the smaller tree to the root of the larger tree. To determine small tree we compare trees by its sizes.
110+
111+
4. Here we attach the smaller tree to the root of the larger tree.
112+
113+
5. We keep sizes of trees in actual states so we update size of larger tree.
114+
115+
Union with optimizations also takes almost **O(1)** time.
116+
117+
As always, some illustrations for better understanding
118+
119+
Before calling `unionSetsContaining(4, and: 7)`:
120+
121+
![BeforeUnion](Images/BeforeUnion.png)
122+
123+
After:
124+
125+
![AfterUnion](Images/AfterUnion.png)
126+
127+
Note that during union caching optimization was performed because of calling `setOf` in the beginning of method.
128+
129+
130+
131+
There are also helper method to just check that two elements is in the same set:
132+
133+
```swift
134+
public mutating func inSameSet(firstElement: T, and secondElement: T) -> Bool {
135+
if let firstSet = setOf(firstElement), secondSet = setOf(secondElement) {
136+
return firstSet == secondSet
137+
} else {
138+
return false
139+
}
140+
}
141+
```
142+
143+
144+
See the playground for more examples of how to use this handy data structure.
145+
146+
147+
## See also
148+
149+
[Union-Find at wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
150+
151+
*Written for Swift Algorithm Club by [Artur Antonov](https://github.com/goingreen)*

0 commit comments

Comments
 (0)
Please sign in to comment.