最小生成树
一、什么是最小生成树?
「树」和「图」最大的区别就是树不会包含环,而图可以包含环。换句话说,树就是「无环连通图」。
那么什么是图的「⽣成树」呢,其实按字⾯意思也好理解,就是在图中找⼀棵包含图中的所有节点的树。专业点说,⽣成树是含有图中所有顶点的「⽆环连通⼦图」。
容易想到,⼀幅图可以有很多不同的⽣成树,⽐如下⾯这幅图,红⾊的边就组成了两棵不同的⽣成树:
对于加权图,每条边都有权重,所以每棵⽣成树都有⼀个权重和。⽐如上图,右侧⽣成树的权重和显然⽐左侧⽣成树的权重和要⼩。
那么最⼩⽣成树很好理解了,所有可能的⽣成树中,权重和最⼩的那棵⽣成树就叫「最⼩⽣成树」。
二、Kruskal算法
所谓最⼩⽣成树,就是图中若⼲边的集合(我们后⽂称这个集合为 mst,最⼩⽣成树的英⽂缩写),你要保证这些边:
1、包含图中的所有节点。
2、形成的结构是树结构(即不存在环)。
3、权重和最⼩。
前两条其实可以很容易地利⽤ Union-Find 算法做到。对于第三点,如何保证得到的生成树是权重和最小的,这里使用到了贪心思路。将所有边按照权重从小到大排序,从权重最小的边开始遍历,如果这条边和mst中的其它边不会形成环,则这条边是最小生成树的一部分,将它加入mst集合;否则,这条边不是最小生成树的一部分,不要把它加入mst集合。这样,最后mst集合中的边就形成了最小生成树,下面我们看一道例题来运用一下 Kruskal 算法。
1584. 连接所有点的最小费用
解法:Kruskal 算法
class UF:
def __init__(self, n):
self.n = n
self.size = [1] * n
self.parents = list(range(n))
def find(self, x: int) -> int:
while x != self.parents[x]:
self.parents[x] = self.parents[self.parents[x]]
x = self.parents[x]
return x
def unionSet(self, x: int, y: int) -> bool:
fx, fy = self.find(x), self.find(y)
if fx == fy:
return False
if self.size[fx] < self.size[fy]:
fx, fy = fy, fx
self.size[fx] += self.size[fy]
self.parents[fy] = fx
return True
def connected(self, p, q):
return self.find(p) == self.find(q)
class Solution:
def minCostConnectPoints(self, points: List[List[int]]) -> int:
# 距离计算函数
dist = lambda x, y: abs(points[x][0] - points[y][0])
+ abs(points[x][1] - points[y][1])
n = len(points)
# 并查集
dsu = UF(n)
edges = list()
for i in range(n):
for j in range(i + 1, n):
edges.append((dist(i, j), i, j))
# 按距离大小从小到大排序
edges.sort()
ret, num = 0, 1
for length, x, y in edges:
# 当前边加入会存在环,舍弃
if dsu.connected(x, y):
continue
ret += length
# 添加该边,更新并查集信息
dsu.unionSet(x, y)
# 节点数量等于n,则符合条件,可提前退出
num += 1
if num == n:
break
return ret
144. 二叉树的前序遍历
解法:DFS
这里使用之前文章所介绍的二叉树前序遍历框架即可。
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
def preorder(root: TreeNode):
if not root:
return
res.append(root.val)
preorder(root.left)
preorder(root.right)
res = list()
preorder(root)
return res