连接所有点的最小费用
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/min-cost-to-connect-all-points
给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。
连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的曼哈顿距离:|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。
请你返回将所有点连接的最小总费用。只有任意两点之间有且仅有一条简单路径时,才认为所有点都已连接。
示例 1:
输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:
我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。
示例 2:
输入:points = [[3,12],[-2,5],[-4,1]]
输出:18
示例 3:
输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4
示例 4:
输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000
示例 5:
输入:points = [[0,0]]
输出:0
提示:
1 <= points.length <= 1000
-106 <= xi, yi <= 106
所有点 (xi, yi) 两两不同。
最小生成树
给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫做生成树。如果边上有权值,那么使得边权和最小的生成树叫做最小生成树。
Prim算法
首先,假设有一棵只包含一个顶点v的树T。然后贪心地选取T和其他顶点之间相连的最小权值的边,并把它加到T中。不断进行这个操作,就可以得到一棵生成树了。通过这个方法得到的生成树就是最小生成树。时间复杂度:O(n2)
抽象出两个集合,集合V和集合Vnew
1、集合V保存未加入到最小生成树中的节点,最开始,图中所有节点都在集合V中。
2、集合Vnew保存已经加入到最小生成树中的节点,如果一个节点加入到了最小生成树中,则将该节点加入到集合Vnew。
说明:Vnew即最小生成树
func minCostConnectPoints(points [][]int) int {
n := len(points) //顶点数
//cost[u][v]表示边e=(u,v)的权值(不存在的情况下设为INF)
cost := make([][]int,n)
for i:=0;i<n;i++{
cost[i] = make([]int,n)
}
for i:=0;i<n;i++{
for j:=i+1;j<n;j++{
dist := abs(points[i][0]-points[j][0])+abs(points[i][1]-points[j][1])
cost[i][j],cost[j][i] = dist,dist
}
}
mincost := make([]int,n) //从集合Vnew出发的边到每个顶点的最小权值
used := make([]bool,n) //顶点i是否包含在集合Vnew中
var prim func() int
prim = func() int{
for i:=0;i<n;i++{
mincost[i] = math.MaxInt32
used[i] = false
}
mincost[0] = 0
res := 0
for true{
v := -1
//从不属于Vnew的顶点中选取从Vnew到其权值最小的顶点
for u:=0;u<n;u++{
if !used[u] && (v==-1 || mincost[u]<mincost[v]){
v=u
}
}
if v == -1{
break
}
used[v] = true //将顶点v加入Vnew中
res += mincost[v] //将边的长度加入结果里
for u:=0;u<n;u++{
mincost[u] = min(mincost[u],cost[v][u])
}
}
return res
}
return prim()
}
func min(a,b int) int{
if a<b{
return a
}
return b
}
func abs(a int) int{
if a<0{
return -a
}
return a
}
Kruskal算法
Kruskal算法按照边的权值的顺序从小到大查看一遍,如果不产生圈(重边等也算在内),就把当前这条边加入到生成树中。
如何判断是否产生圈:假设现在要把连接顶点u和顶点v的边e加入生成树中。如果加入之前u和v不在同一个连通分量里,那么加人e也不会产生圈。反之,如果u和v在同一个连通分量里,那么一定会产生圈。可以使用并查集高效地判断是否属于同一个连通分量。
Kruskal算法在边的排序上最费时,当使用时间复杂度为O(eloge)的排序方法时,Kruskal算法的时间复杂度即为O(eloge),所以,适合于求边稀疏的网的最小生成树。
func minCostConnectPoints(points [][]int) int {
n := len(points)
type edge struct{
u,v,dist int
}
edges := []edge{}
for i:=0;i<n;i++{
for j:=i+1;j<n;j++{
edges = append(edges,edge{i,j,dis(points[i],points[j])})
}
}
sort.Slice(edges, func(i, j int) bool { return edges[i].dist < edges[j].dist })
uf := newUnionFind(n)
left := n - 1
res := 0
for _, e := range edges {
if uf.union(e.u, e.v) {
res += e.dist
left--
if left == 0 {
break
}
}
}
return res
}
func dis(p, q []int) int {
return abs(p[0]-q[0]) + abs(p[1]-q[1])
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
type unionFind struct {
parent, rank []int
}
func newUnionFind(n int) *unionFind {
parent := make([]int, n)
rank := make([]int, n)
for i := range parent {
parent[i] = i
rank[i] = 1
}
return &unionFind{parent, rank}
}
func (uf *unionFind) find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.find(uf.parent[x])
}
return uf.parent[x]
}
func (uf *unionFind) union(x, y int) bool {
fx, fy := uf.find(x), uf.find(y)
if fx == fy {
return false
}
if uf.rank[fx] < uf.rank[fy] {
fx, fy = fy, fx
}
uf.rank[fx] += uf.rank[fy]
uf.parent[fy] = fx
return true
}
做个笔记,如有错误欢迎大家指正!