zoukankan      html  css  js  c++  java
  • 图---并查集和最小生成树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
    }
    
  • 相关阅读:
    Java怎样对一个属性设置set或get方法的快捷键
    小程序怎样控制rich-text中的<img>标签自适应
    Java中Arrys数组常用的方法
    Java 怎样实现调用其他方法
    Java保留两位小数
    解决ajax请求跨域
    rand(7) 到rand(10)
    c++生成随机数
    批量该文件名
    正则表达式(=)
  • 原文地址:https://www.cnblogs.com/cplemom/p/14322816.html
Copyright © 2011-2022 走看看