图---并查集和最小生成树Kruskal算法
并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。
并查集的实现原理也比较简单,就是使用树来表示集合,树的每个节点就表示集合中的一个元素,树根对应的元素就是该集合的代表。
并查集实现
并查集的基本操作有三个:
makeSet(s)
:建立一个新的并查集,其中包含 s 个单元素集合。
parent := make([]int, len(edges)+1)
var makeSet func(nums []int)
makeSet = func(nums []int) {
for i := range nums {
nums[i] = i
}
}
find(x)
:找到元素 x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。
路径压缩:为了加快查找速度,查找时将x到根节点路径上的所有点的parent设为根节点。
unionSet(x, y)
:把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果相交则不合并。
按秩合并:使用秩来表示树的高度,在合并时,总是将具有较小秩的树根指向具有较大秩的树根。简单的说,就是总是将比较矮的树作为子树,添加到较高的树中。
为了保存秩,需要额外使用一个与 parent
同长度的数组rank
,并将所有元素都初始化为 0。
rank:= make([]int,size)
var union func(x, y int) bool
union = func(x, y int) bool {
x, y = find(x), find(y)
if x == y {
return false
}
if rank[x] > rank[y] {
parent[y] = x
} else {
parent[x] = y
//提高秩等级
if rank[x] == rank[y] {
rank[y]++
}
}
return true
}
用途
- 维护无向图的连通性,判断两个点是否在同一个连通块
- 判断新增一条边,是否会产生环
- 计算最后有多少个不相交的集合,既计算有多少个根节点(
parent[i]==i
)
最小生成树
由于最小生成树本身是一棵生成树,所以需要时刻满足以下两点:
- 生成树中任意顶点之间有且仅有一条通路,也就是说,生成树中不能存在回路;
- 对于具有 n 个顶点的连通网,其生成树中只能有 n-1 条边,这 n-1 条边连通着 n 个顶点。
连接 n 个顶点在不产生回路的情况下,只需要 n-1 条边。
Kruskal算法
将所有边按照权值的大小进行升序排序,然后从小到大一一判断。条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。
从这里就可以看出,并查集就完全可以用于鉴定两个顶点相连是否会产生回路。
题目样例
给你一个points
数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi]
。
连接点 [xi, yi]
和点 [xj, yj]
的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj|
,其中 |val|
表示 val
的绝对值。
请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。
输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:我们可以按照上图所示连接所有点得到最小总费用,总费用为20。注意到任意两个点之间只有唯一一条路径互相到达。
func minCostConnectPoints(points [][]int) int {
//并查集
n := len(points)
parent := make([]int, n)
for i := range points {
parent[i] = i
}
var find func(x int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
var union func(x, y int) bool
union = func(x, y int) bool {
x, y = find(x), find(y)
if x == y {
return false
}
parent[x] = y
return true
}
//循环遍历所有点,生成所有可能的边,并按距离从小到大排序
type edge struct{ v, w, dis int }
edges := make([]edge, 0)
for i := range points {
for j := i + 1; j < n; j++ {
edges = append(edges, edge{i, j, getDistance(points[i][0], points[i][1], points[j][0], points[j][1])})
}
}
sort.Slice(edges, func(i, j int) bool {
return edges[i].dis < edges[j].dis
})
//循环遍历所有的边,直到所有的点都已经连接在一起,就是最小生成树
ans := 0
for _, p := range edges {
if union(p.v, p.w) {
n--
ans += p.dis
if n == 0 {
break
}
}
}
return ans
}
func getDistance(x1, y1, x2, y2 int) int {
if x1 > x2 {
x1, x2 = x2, x1
}
if y1 > y2 {
y1, y2 = y2, y1
}
return x2 - x1 + y2 - y1
}