NOTE

2.14 UnionFind

1. 是什么 - 一种树结构,区别在于是孩子指向父亲 2. 有什么用 - 解决连接问题(比路径问题更加简单) 3. 实现 3.1. API 3.2. Quick Find 3.2.1. 测试 3.3. Quick Union - 3.3.1. 测试 3.4. 基于size的优化 3.4.1. 测试

Data Structures & Algorithms创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 是什么

  • 一种树结构,区别在于是孩子指向父亲

2. 有什么用

  • 解决连接问题(比路径问题更加简单)

3. 实现

3.1. API

type IUnionFind interface {
	// 获取并查集的长度
	GetSize() int
	// id为p和id为q的元素是否属于同一个集合
	IsConnected(p int, q int) (bool,error)
	// 合并id为p和id为q的元素到一个集合
	UnionElements(p int, q int) error
}

3.2. Quick Find


import "fmt"

type QuickFind struct {
	//index表示id,value表示id所属的集合
	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)
// 查找id为p和元素和id为q的元素是否同一个集合
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

}

// 查找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)
// 合并id为p和id为q的元素到一个集合
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. 测试

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),h为树的高度
// 查找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),h为树的高度
// 查找id为p和元素和id为q的元素是否同一个集合
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),h为树的高度
// 合并id为p和id为q的元素到一个集合
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. 测试

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的优化

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),h为树的高度
// 查找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),h为树的高度
// 查找id为p和元素和id为q的元素是否同一个集合
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),h为树的高度
// 合并id为p和id为q的元素到一个集合
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
	}

	//将元素少的集合合并到元素多的集合上
	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. 测试

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的优化

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),h为树的高度
// 查找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),h为树的高度
// 查找id为p和元素和id为q的元素是否同一个集合
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),h为树的高度
// 合并id为p和id为q的元素到一个集合
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
	}

	//将元素少的集合合并到元素多的集合上
	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. 测试

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. 路径压缩

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),h为树的高度
// 查找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),h为树的高度
// 查找id为p和元素和id为q的元素是否同一个集合
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),h为树的高度
// 合并id为p和id为q的元素到一个集合
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
	}

	//将元素少的集合合并到元素多的集合上
	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. 测试

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))
}