NOTE
2.14 UnionFind
Union-Find, Quick Find, Quick Union, and optimizations.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What It Is
- A tree structure. The difference is that children point to parents
2. What It Is Used For
- Solving connectivity problems (simpler than path problems)
3. Implementation
3.1. API
type IUnionFind interface {
// Get the number of elements in the union-find
GetSize() int
// Whether the elements with ids p and q belong to the same set
IsConnected(p int, q int) (bool,error)
// Merge the elements with ids p and q into the same set
UnionElements(p int, q int) error
}
3.2. Quick Find
import "fmt"
type QuickFind struct {
// index is the id; value is the set that the id belongs to
id []int
}
func NewQuickFind(size int) *QuickFind {
quickFind := &QuickFind{id: make([]int, size)}
for i := 0; i < len(quickFind.id); i++ {
quickFind.id[i] = i
}
return quickFind
}
func (qf *QuickFind) GetSize() int {
return len(qf.id)
}
// O(1)
// Check whether the elements with ids p and q belong to the same set
func (qf *QuickFind) IsConnected(p int, q int) (bool, error) {
setP, err := qf.find(p)
if err != nil {
return false, err
}
setQ, err := qf.find(q)
if err != nil {
return false, err
}
return setQ == setP, nil
}
// Find the set corresponding to the element with id p
func (qf *QuickFind) find(p int) (int, error) {
if p < 0 || p >= len(qf.id) {
return 0, fmt.Errorf("out of bound")
}
return qf.id[p], nil
}
// O(N)
// Merge the elements with ids p and q into the same set
func (qf *QuickFind) UnionElements(p int, q int) error {
setP, err := qf.find(p)
if err != nil {
return err
}
setQ, err := qf.find(q)
if err != nil {
return err
}
if setQ == setP {
return nil
}
for i := 0; i < len(qf.id); i++ {
if qf.id[i] == setP {
qf.id[i] = setQ
}
}
return nil
}
3.2.1. Test
func TestQuickFind(t *testing.T) {
quickFind := NewQuickFind(10)
fmt.Println(quickFind.GetSize())
fmt.Println(quickFind.IsConnected(1,2))
quickFind.UnionElements(1,2)
fmt.Println(quickFind.IsConnected(1,2))
}
3.3. Quick Union
package unionfind
import "fmt"
type QuickUnion struct {
parent []int
}
func NewQuickUnion(size int) *QuickUnion {
quickUnion := &QuickUnion{parent: make([]int, size)}
for i := 0; i < size; i++ {
quickUnion.parent[i] = i
}
return quickUnion
}
func (qu *QuickUnion) GetSize() int {
return len(qu.parent)
}
// O(h), where h is the height of the tree
// Find the set corresponding to the element with id p
func (qu *QuickUnion) find(p int) (int, error) {
if p < 0 || p >= len(qu.parent) {
return 0, fmt.Errorf("out of bound")
}
for p != qu.parent[p] {
p = qu.parent[p]
}
return p, nil
}
// O(h), where h is the height of the tree
// Check whether the elements with ids p and q belong to the same set
func (qu *QuickUnion) IsConnected(p int, q int) (bool, error) {
setP, err := qu.find(p)
if err != nil {
return false, err
}
setQ, err := qu.find(q)
if err != nil {
return false, err
}
return setQ == setP, nil
}
// O(h), where h is the height of the tree
// Merge the elements with ids p and q into the same set
func (qu *QuickUnion) UnionElements(p int, q int) error {
setP, err := qu.find(p)
if err != nil {
return err
}
setQ, err := qu.find(q)
if err != nil {
return err
}
if setQ == setP {
return nil
}
qu.parent[setP] = setQ
return nil
}
3.3.1. Test
func TestQuickUnion(t *testing.T) {
quickUnion := NewQuickUnion(10)
fmt.Println(quickUnion.GetSize())
fmt.Println(quickUnion.IsConnected(1,2))
quickUnion.UnionElements(1,2)
fmt.Println(quickUnion.IsConnected(1,2))
}
3.4. Size-Based Optimization
package unionfind
import "fmt"
type QuickUnion2 struct {
parent []int
sz []int
}
func NewQuickUnion2(size int) *QuickUnion2 {
quickUnion2 := &QuickUnion2{
parent: make([]int, size),
sz: make([]int, size)}
for i := 0; i < size; i++ {
quickUnion2.parent[i] = i
quickUnion2.sz[i] = 1
}
return quickUnion2
}
func (qu *QuickUnion2) GetSize() int {
return len(qu.parent)
}
// O(h), where h is the height of the tree
// Find the set corresponding to the element with id p
func (qu *QuickUnion2) find(p int) (int, error) {
if p < 0 || p >= len(qu.parent) {
return 0, fmt.Errorf("out of bound")
}
for p != qu.parent[p] {
p = qu.parent[p]
}
return p, nil
}
// O(h), where h is the height of the tree
// Check whether the elements with ids p and q belong to the same set
func (qu *QuickUnion2) IsConnected(p int, q int) (bool, error) {
setP, err := qu.find(p)
if err != nil {
return false, err
}
setQ, err := qu.find(q)
if err != nil {
return false, err
}
return setQ == setP, nil
}
// O(h), where h is the height of the tree
// Merge the elements with ids p and q into the same set
func (qu *QuickUnion2) UnionElements(p int, q int) error {
setP, err := qu.find(p)
if err != nil {
return err
}
setQ, err := qu.find(q)
if err != nil {
return err
}
if setQ == setP {
return nil
}
// Merge the smaller set into the larger set
if qu.sz[setP] < qu.sz[setQ] {
qu.parent[setP] = setQ
qu.sz[setQ] += qu.sz[setP]
} else {
qu.parent[setQ] = setP
qu.sz[setP] += qu.sz[setQ]
}
return nil
}
3.4.1. Test
func TestQuickUnion2(t *testing.T) {
quickUnion := NewQuickUnion2(10)
fmt.Println(quickUnion.GetSize())
fmt.Println(quickUnion.IsConnected(1,2))
quickUnion.UnionElements(1,2)
fmt.Println(quickUnion.IsConnected(1,2))
}
3.5. Rank-Based Optimization
package unionfind
import "fmt"
type QuickUnion3 struct {
parent []int
rank []int
}
func NewQuickUnion3(size int) *QuickUnion3 {
quickUnion := &QuickUnion3{
parent: make([]int, size),
rank: make([]int, size)}
for i := 0; i < size; i++ {
quickUnion.parent[i] = i
quickUnion.rank[i] = 1
}
return quickUnion
}
func (qu *QuickUnion3) GetSize() int {
return len(qu.parent)
}
// O(h), where h is the height of the tree
// Find the set corresponding to the element with id p
func (qu *QuickUnion3) find(p int) (int, error) {
if p < 0 || p >= len(qu.parent) {
return 0, fmt.Errorf("out of bound")
}
for p != qu.parent[p] {
p = qu.parent[p]
}
return p, nil
}
// O(h), where h is the height of the tree
// Check whether the elements with ids p and q belong to the same set
func (qu *QuickUnion3) IsConnected(p int, q int) (bool, error) {
setP, err := qu.find(p)
if err != nil {
return false, err
}
setQ, err := qu.find(q)
if err != nil {
return false, err
}
return setQ == setP, nil
}
// O(h), where h is the height of the tree
// Merge the elements with ids p and q into the same set
func (qu *QuickUnion3) UnionElements(p int, q int) error {
setP, err := qu.find(p)
if err != nil {
return err
}
setQ, err := qu.find(q)
if err != nil {
return err
}
if setQ == setP {
return nil
}
// Merge the smaller set into the larger set
if qu.rank[setP] < qu.rank[setQ] {
qu.parent[setP] = setQ
} else if qu.rank[setP] > qu.rank[setQ] {
qu.parent[setQ] = setP
} else {
qu.parent[setQ] = setP
qu.rank[setP] += 1
}
return nil
}
3.5.1. Test
func TestQuickUnion3(t *testing.T) {
quickUnion := NewQuickUnion3(10)
fmt.Println(quickUnion.GetSize())
fmt.Println(quickUnion.IsConnected(1,2))
quickUnion.UnionElements(1,2)
fmt.Println(quickUnion.IsConnected(1,2))
}
3.6. Path Compression
package unionfind
import "fmt"
type QuickUnion4 struct {
parent []int
rank []int
}
func NewQuickUnion4(size int) *QuickUnion4 {
quickUnion := &QuickUnion4{
parent: make([]int, size),
rank: make([]int, size)}
for i := 0; i < size; i++ {
quickUnion.parent[i] = i
quickUnion.rank[i] = 1
}
return quickUnion
}
func (qu *QuickUnion4) GetSize() int {
return len(qu.parent)
}
// O(h), where h is the height of the tree
// Find the set corresponding to the element with id p
func (qu *QuickUnion4) find(p int) (int, error) {
if p < 0 || p >= len(qu.parent) {
return 0, fmt.Errorf("out of bound")
}
for p != qu.parent[p] {
qu.parent[p] = qu.parent[qu.parent[p]]
p = qu.parent[p]
}
return p, nil
}
// O(h), where h is the height of the tree
// Check whether the elements with ids p and q belong to the same set
func (qu *QuickUnion4) IsConnected(p int, q int) (bool, error) {
setP, err := qu.find(p)
if err != nil {
return false, err
}
setQ, err := qu.find(q)
if err != nil {
return false, err
}
return setQ == setP, nil
}
// O(h), where h is the height of the tree
// Merge the elements with ids p and q into the same set
func (qu *QuickUnion4) UnionElements(p int, q int) error {
setP, err := qu.find(p)
if err != nil {
return err
}
setQ, err := qu.find(q)
if err != nil {
return err
}
if setQ == setP {
return nil
}
// Merge the smaller set into the larger set
if qu.rank[setP] < qu.rank[setQ] {
qu.parent[setP] = setQ
} else if qu.rank[setP] > qu.rank[setQ] {
qu.parent[setQ] = setP
} else {
qu.parent[setQ] = setP
qu.rank[setP] += 1
}
return nil
}
3.6.1. Test
func TestQuickUnion4(t *testing.T) {
quickUnion := NewQuickUnion4(10)
fmt.Println(quickUnion.GetSize())
fmt.Println(quickUnion.IsConnected(1,2))
quickUnion.UnionElements(1,2)
fmt.Println(quickUnion.IsConnected(1,2))
}


Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub