并查集定义及习题

并查集

定义

  • 并查集(union & find)是一种树型数据结构,用于处理一些不交集的合并及查询问题。
  • Find:确定元素属于哪一个子集,它可以被用来确定两个元素是否属于同一个子集。
  • Union:将两个子集合并成同一个集合(不支持拆分)。

场景

  • 小弟 - > 老大
  • 帮派识别

优化(路径压缩)

  • 第一种方式,在Union时,将深度低的挂到深度高的,需要多一个rank属性辅助
  • 第二种方式,在Find时,如果x老大是y,则直接将x - > y的路径上所有节点都直接指向y

代码

  • 采取第二种路径压缩方式。
type QuickUnionUF struct {
	roots []int
}

func ConstructorUF(n int) *QuickUnionUF {
	roots := make([]int, n)
	// 自己老大是自己
	for i := 0; i < n; i++ {
		roots[i] = i
	}
	return &QuickUnionUF{
		roots: roots,
	}
}

func (this *QuickUnionUF) findRoot(i int) int {
	root := i
	// 找到老大
	for root != this.roots[root] {
		root = this.roots[root]
	}
	// 找寻路径上的所有结点直接指向老大
	for i != this.roots[i] {
		this.roots[i], i = root, this.roots[i]
	}
	return root
}


func (this *QuickUnionUF) connected(x, y int) bool {
	// 两者是不是一个老大
	return this.findRoot(x) == this.findRoot(y)
}

func (this *QuickUnionUF) union(x, y int) {
	// 将两者指向一个老大
	xRoot, yRoot := this.findRoot(x), this.findRoot(y)
	this.roots[xRoot] = yRoot
}

习题

leetcode 200. 岛屿数量

并查集解法
func numIslands(grid [][]byte) int {
	n := len(grid)
	if n == 0 {
		return 0
	}
	m := len(grid[0])
	uf := ConstructorUF(m * n)
	tmp := [4][2]int{{0, 1}, {0, -1}, {-1, 0}, {1, 0}}
	var ret, nr, nc int
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if grid[i][j] == '1' {
				for _, d := range tmp {
					nr, nc = i+d[0], j+d[1]
					// 周边节点都和当前点是一个老大
					if nr >= 0 && nc >= 0 && nr < n && nc < m && grid[nr][nc] == '1' {
						uf.union(i*m+j, nr*m+nc)
					}
				}
			}
		}
	}
	// 求老大数量,即自己的老大是自己
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if grid[i][j] == '1' {
				if i*m+j == uf.findRoot(i*m+j) {
					ret++
				} 
			}
		}
	}
	return ret
}
深度优先搜索(染色)解法
func numIslands(grid [][]byte) int {
	n := len(grid)
	if n == 0 {
		return 0
	}
	m := len(grid[0])
	var ret int
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if grid[i][j] == '1' {
				ret++
				// 染色之后,如果再遇到没染色的,则是新岛屿
				dfs(i, j, n, m, grid)
			}
		}
	}
	return ret
}

func dfs(x, y, n, m int, g [][]byte) {
	if x < 0 || x > n-1 || y < 0 || y > m-1 {
		return
	}
	if g[x][y] == '1' {
		g[x][y] = '0'
		// 判断四周
		dfs(x-1, y, n, m, g)
		dfs(x+1, y, n, m, g)
		dfs(x, y-1, n, m, g)
		dfs(x, y+1, n, m, g)
	}
}
广度优先搜索
func numIslands(grid [][]byte) int {
	n := len(grid)
	if n == 0 {
		return 0
	}
	m := len(grid[0])
	var ret, x, y int
	var queue []int
	tmp := [4][2]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if grid[i][j] == '1' {
				grid[x][y] = '0'
				ret++
				queue = append(queue, i*m+j)
				for len(queue) > 0 {
					// 出队
					x, y = queue[0]/m, queue[0]%m
					queue = queue[1:]

					// 向四周扩散
					for _,d:=range tmp {
						xx, yy := x+d[0], y+d[1]
						if xx >= 0 && xx <= n-1 && yy >= 0 && yy <= m-1 && grid[xx][yy] == '1' {
							// 关键!不加下一行会引起很多重复便利,导致内存溢出
							grid[xx][yy] = '0'
							queue = append(queue, xx*m+yy)
						}
					}
				}
			}
		}
	}
	return ret
}

547. 朋友圈

并查集解法
func findCircleNum(M [][]int) int {
	n := len(M)
	var ret int
	uf := ConstructorUF(n * n)

	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			// 当前值是1,则将对角线上的值连接
			if M[i][j] == 1 {
				uf.union(i*n+j,i*n+i)
				uf.union(i*n+j,j*n+j)
			}
		}
	}
	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			if M[i][j] == 1 && i*n+j == uf.findRoot(i*n+j){
				ret++
			}
		}
	}
	
	return ret
}

深度优先搜索

func findCircleNum(M [][]int) int {
	n := len(M)
	var ret int

	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			if M[i][j] == 1 {
				ret++
				dfs(i,n,M)
			}
		}
	}

	return ret
}

func dfs(x, n int, M [][]int) {
	for i := 0; i < n; i++ {
		// 对于每一个是1的点,将它所属行和所属列的点全部染色
		if M[x][i] == 1 {
			M[x][i] = 0
			M[i][x] = 0
			dfs(i, n, M)
		}
	}
}

广度优先搜索

func findCircleNum(M [][]int) int {
	n := len(M)
	var ret, x int
	var queue []int

	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			if M[i][j] == 1 {
				ret++
				M[j][i] = 0
				M[i][j] = 0
				queue = append(queue, i)
				for len(queue) != 0 {
					x = queue[0]
					queue = queue[1:]
					for a := 0; a < n; a++ {
						if M[x][a] == 1 {
							M[x][a] = 0
							M[a][x] = 0
							queue = append(queue, a)
						}
					}
				}
			}
		}
	}
	return ret
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值