欢迎光临
我们一直在努力

算法学习笔记(3):最小生成树

最小生成树

最小生成树是图论的算法之一,用于解决带有权重且无环的最小连通问题。我们要计算连通所有节点的最小值,如果定义连通无向图

G

=

(

V

,

E

)

G=(V,E)

G=(V,E), 其中,

V

E

V和E

VE分别表示节点和节点所有的可能连接。对于每条边

(

u

,

v

)

E

(u, v)∈E

(u,v)E,都赋予权重

w

=

(

u

,

v

)

w=(u,v)

w=(u,v),作为连接节点u和节点v的代价。希望找到一个无环子集

T

E

T\\subseteq{E}

TE,即能够将所有节点连接起来,又具有最小权重,也即

w

(

T

)

=

(

u

,

v

)

T

w

(

u

,

v

)

w(T)=\\sum\\limits_{(u,v)\\in{T}}{w(u,v)}

w(T)=(u,v)Tw(u,v),很显然,无环子集T是一棵树,因为T是由图G生成的,求取该生成树的问题为最小生成问题。

如下所示: 在这里插入图片描述

接下来将使用两种贪心算法,分别为Kruskal和Prim算法,找到最小生成树T(最小生成树并不唯一)。在介绍两种算法之前,先介绍安全边和 安全边:A是某棵树的最小生成树的一个子集,如果边(u, v)加入到集合A中,如果

A

(

u

,

v

)

A\\cup{(u,v)}

A(u,v)也是某棵树的最小生成树的子集,由于可以安全地将边(u,v)加入到集合A而不会破坏集合A的循环不变式,称这样的边为安全边。

切割:对于无向图

G

=

(

V

,

E

)

G = (V,E)

G=(V,E)的一个切割

(

S

,

V

S

)

(S, V-S)

(S,VS)是集合V 的一个划分,如果某一条边(u,v)的一个端点位于集合S,而另一个端点位于集合V-S,那么就称该条边横跨切割(S,V-S)。如果集合A中不存在横跨该切割的边,称该切割尊重集合A。在横跨一个切割的所有边中,权重最小的边称为轻量级边。示意图如下所示: 切割示意图

定理1:如果集合A为某个连通无向图

G

=

(

V

,

E

)

G=(V,E)

G=(V,E)的一个子集,且边E定义了实数权重函数w。A包括在图G的某棵最小生成树中,设(S,V-S)是图G中尊重集合A的任意一个切割,又设(u,v)是横框切割(S,V-S)的一条轻量级边,那么边(u,v)对于集合A是安全的。也就是说,找到某个尊重集合A的切割中的轻量级边,将该轻量级边加入到集合A中,集合A仍然是安全的。

Kruskal算法和Prim算法都是对找到安全边的细化。对于Kruskal算法,集合A是一个森林,节点就是给定图的节点。对于Prim算法,结合A则是一颗树,每次加入到A中的安全边永远是连接A和A之外某个节点的边中权重最小的边。可以发现,Kruskal和Prim算法都是贪心算法。接下来将对两个算法详细介绍。

Kruskal算法

Kruskal找到安全边的方法是,在所有连接森林中两颗不同树的边里面,找到权重最小的那一条边

u

v

(u,v)

uv。假设C1和C2为边(u,v)所连接的两棵树,由于边(u,v)一定是连接C1和某棵树的轻量级边,由定理1可以推出,边(u,v)是C1的一条安全边。

模板

def kruskal(edges:List[List[int]], n:int , m: int) >:
# n个节点,m条边
# edges存储的格式为:edge[i] = [w,u,v],u和v分别为两个端点,w为权重

edges.sort(key lambda x: x[0])
parent = [i for i in range(n)]
def find(x):
if x != fa[x]:
parent[x] = find(parent[x])
return parent[x]
def union(x,y):
parent[find(x)] = find(y)

ans = 0
mst_edges = []
for w, u, v in edges:
# 如果 u 和 v 是不连通的,那么权重最小的切割w对于find(u)是安全的
if find(u) != find(v):
union(u,v)
ans += w
mst_edges.append((u,v,w))
return ans

Kruskal算法流程示意图: 在这里插入图片描述

Prim算法

集合A中的边总是构成一颗树,从树中的任意一个节点r开始,一直长大到覆盖V中的所有节点为止。在连接集合A和集合A之外任意节点的所有边中,选择一条轻量级边加入到集合A中。

def prim(edges, n,m) :
graph = [[] for _ in range(n)]
for u, v, w in edges:
graph[u].append((v,w))
graph[v].append((u,w))
visited = [False] * n

min_heap = [(0,1,0)]
#创建优先队列,格式为:(权重,当前节点,当前边终点),以权重作为首先排序,从节点0开始

ans = 0
mst_edges = []
while min_heap and len(mst_edges) < n 1:
# 每次弹出的边,都是当前权重最小的边,并且以节点 0 作为起始节点
w,u,v = heapq.heappop(min_heap)
if visited[v]:
continue

visited[v] = True
ans += w

# 如果 u == -1 ,说明不是起点
if u != 1:
mst_edegs.append((u,v,w))

# v是新加入到 最小生成树的终点,遍历 v 的所有邻边
for next_node, weight in graph[v]:
if not visited[next_node]:
# 如果 next_node 没有加入生成树,那么这条边就是候选边,加入到最小堆中
heap.heanppush(heap, (weight, v, next_node))
if len(mst_edges) != n 1:
return 1, []
return ans, mst_edges

Prim算法流程示意图 Prim算法流程示意图

相关题目

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

1584. 连接所有点的最小费用 – 力扣(LeetCode) 给你一个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) 两两不同。

import heapq
class Solution:
def minCostConnectPoints(self, points: List[List[int]]) > int:
# Prim解法
dist = lambda x,y: abs(points[x][0] points[y][0]) + abs(points[x][1] points[y][1])
n = len(points)
edges = []
mst_edges = []
graph = [[]for _ in range(n)]
for i in range(n):
for j in range(i + 1, n):
edges.append((dist(i,j),i,j))
for w,u,v in edges:
graph[u].append((v,w))
graph[v].append((u,w))
visited = [False] * n

min_heap = [(0, 1, 0)]
ans = 0
while min_heap and len(mst_edges) < n 1:
w, u, v = heapq.heappop(min_heap)
if visited[v]:
continue

visited[v] = True
ans += w
if u != 1:
mst_edges.append((w,u,v))

for next_node, weight in graph[v]:
heapq.heappush(min_heap,(weight, v, next_node))
if len(mst_edges) != n 1:
return ans, []
return ans

## Kruskal 解法
# n = len(points)
# ans = 0
# edges = []
# mst_edges = []
# parent = list(range(n))

# def find(x):
# if parent[x] != x:
# parent[x] = find(parent[x])
# return parent[x]

# def union(x, y):
# parent[find(y)] = find(x)
# # 自己构造 edges
# dist = lambda x,y: abs(points[x][0] – points[y][0]) + abs(points[x][1] – points[y][1])
# for i in range(n):
# for j in range(i + 1, n):
# edges.append((dist(i, j), i, j))

# # 需要对权重值进行排序
# edges.sort()
# for w, u, v in edges:
# if find(u) != find(v):
# union(u, v)
# ans += w
# mst_edges.append((w, u, v))
# return ans

赞(0)
未经允许不得转载:171主机测评 » 算法学习笔记(3):最小生成树
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址