图---并查集和最小生成树Kruskal算法

并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。

并查集的实现原理也比较简单,就是使用树来表示集合,树的每个节点就表示集合中的一个元素,树根对应的元素就是该集合的代表。

并查集实现

并查集的基本操作有三个:

  1. 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
	}
}
  1. find(x):找到元素 x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

路径压缩:为了加快查找速度,查找时将x到根节点路径上的所有点的parent设为根节点。

  1. 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
}
用途
  1. 维护无向图的连通性,判断两个点是否在同一个连通块
  2. 判断新增一条边,是否会产生环
  3. 计算最后有多少个不相交的集合,既计算有多少个根节点(parent[i]==i)

最小生成树

由于最小生成树本身是一棵生成树,所以需要时刻满足以下两点:

  • 生成树中任意顶点之间有且仅有一条通路,也就是说,生成树中不能存在回路;
  • 对于具有 n 个顶点的连通网,其生成树中只能有 n-1 条边,这 n-1 条边连通着 n 个顶点。

连接 n 个顶点在不产生回路的情况下,只需要 n-1 条边。

Kruskal算法

将所有边按照权值的大小进行升序排序,然后从小到大一一判断。条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。

从这里就可以看出,并查集就完全可以用于鉴定两个顶点相连是否会产生回路。

题目样例

1584. 连接所有点的最小费用

给你一个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
}
posted @ 2021-01-24 22:54  傅小灰  阅读(491)  评论(1编辑  收藏  举报