代码随想录算法训练营第五十七天 | 最小生成树之prim算法精讲、最小生成树之kruskal算法精讲

一、prim算法精讲

题目连接:53. 寻宝(第七期模拟笔试) (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——最小生成树之prim

最小生成树:所有节点的最小连通子图,即以最小的边的权重将图中所有的点连接起来。(花最小的成本连通所有的点)

最小生成树任务:如果图中有 n 个节点,那么一定可以用 n - 1 条边将所有节点连接起来,最小生成树就是找到这 n - 1 条边。

prim算法定义:从节点的角度,采用贪心的策略,每次寻找距离最小生成树最近的节点,并加入到最小生成树中。

prim三部曲:
step1. 选距离生成树最近的节点
step2. 最近节点加入生成树
sept3. 更新非生成树节点到生成树的最短距离(即更新minDist数组)

minDist数组:用来记录每一个节点距离最小生成树的最近距离。

# v -> 节点数量
# e -> 边数量 
# edges -> 存储边信息的列表,每一个边是一个元组(x, y, k),表示从节点 x 到节点 y 的权重为 k
def prim(v,e, edges):
    # 初始化邻接矩阵,因为要求最小值,所以设置为正无穷
    grid = [[float('inf')] * (v + 1) for _ in range(v + 1)]
    
    
    # 填充邻接矩阵
    for edge in edges:
        x, y, k = edge
        grid[x][y] = k  # 从 x 到 y 的权重
        grid[y][x] = k  # 从 y 到 x 的权重
    
    # 初始化 minDist 数组,用来记录当前节点到最小生成树的最短距离
    minDist = [float('inf')] * (v + 1)
    # 从1节点开始,到自己的距离为 0 
    minDist[1] = 0
    
    # 记录节点是否在树种
    isInTree = [False] * (v + 1)
    
    # prim主循环,要循环 (v - 1) 次
    for i in range(1, v):
        
        # step1. 找到距离生成树最近的节点
        cur = -1    # 初始化,找到当前未在生成树中的离树最近的节点
        minVal = float('inf')   # 初始化最小值为无穷大
        
        # 遍历从 1 到 v 的所有节点
        for j in range(1, v + 1):
            # 如果不在树内 且 距离比最小值更小
            if not isInTree[j] and minDist[j] < minVal:
                # 更新最小值为当前值,且更新当前节点
                minVal = minDist[j]
                cur = j
                
        # step2. 将当前选中的最近节点加入生成树
        isInTree[cur] = True
        
        # step3. 更新非生成树节点到生成树的最小距离
        for j in range(1, v + 1):
            if not isInTree[j] and grid[cur][j] < minDist[j]:
                minDist[j] = grid[cur][j]
                
    # 统计最小生成树的权重和
    result = sum(minDist[2:v+1])
    
    return result
    
if __name__ == '__main__':
    v, e = map(int,input().split())
    edges = []
    
    # 遍历每一条边
    for _ in range(e):
        x, y, k = map(int, input().split())
        edges.append((x, y, k))
        
    print(prim(v, e, edges))
"""
扩展,需要输出边
"""
def prim(v,e, edges):
    grid = [[float('inf')] * (v + 1) for _ in range(v + 1)]
    
    for edge in edges:
        x, y, k = edge
        grid[x][y] = k  
        grid[y][x] = k  

    minDist = [float('inf')] * (v + 1)
    minDist[1] = 0
    
    isInTree = [False] * (v + 1)
    
    # 改动,增加数组记录边
    parent = [-1] * (v + 1)
    
    for i in range(1, v):
        
        cur = -1   
        minVal = float('inf') 
        
        for j in range(1, v + 1):
            if not isInTree[j] and minDist[j] < minVal:
                minVal = minDist[j]
                cur = j
                
        isInTree[cur] = True
        
        for j in range(1, v + 1):
            if not isInTree[j] and grid[cur][j] < minDist[j]:
                minDist[j] = grid[cur][j]
                # 改动,记录边,注意数组指向的顺序 j -> cur
                parent[j] = cur
    
    # 改动,输出边   
    for i in range(1, v + 1):
        print(f"{i} -> {parent[i]}")
    
if __name__ == '__main__':
    v, e = map(int,input().split())
    edges = []
    
    for _ in range(e):
        x, y, k = map(int, input().split())
        edges.append((x, y, k))
        
    print(prim(v, e, edges))

二、kruskal算法精讲

题目连接:53. 寻宝(第七期模拟笔试) (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——最小生成树之kruskal

kruskal思路:
· 先按边的权值进行排序;
· 再遍历排序后的边
        如果边首位两个节点在同一个集合,说明会形成环。
        如果边首尾两个节点不在同一个集合,加入最小生成树,并把两个节点加入同一集合。

Note:判断是否在同一集合,用到并查集的内容。

class UnionFindSet:
    def __init__(self, n):
        self.parent = list(range(n))
        
    def find(self, u):
        if self.parent[u] != u:
            self.parent[u] = self.find(self.parent[u])
        return self.parent[u]
        
    def isSame(self, u, v):
        u = self.find(u)
        v = self.find(v)
        return u == v
        
    def join(self, u, v):
        root_u = self.find(u)
        root_v = self.find(v)
        if root_u != root_v:
            self.parent[root_v] = root_u

def kruskal(v, edges):
    # step1. 按边的权重大小,从小到大重新排序
    # edge 是一个(v1, v2, val)的元组,v1 和 v2 分别是边的两个端点,val 是边的权重
    edges.sort(key=lambda edge: edge[2])
    
    # step2. 初始化并查集,编号从 1 到 v 
    ufs = UnionFindSet(v + 1)
    
    # 记录最终权重总和
    result = 0
    
    # step3. 遍历所有边,用并查集查找是否属于同一集合
    for edge in edges:
        v1, v2, val = edge  # 解包元组,获取边的两个端点和权值
        x = ufs.find(v1)    # 查找边的一个端点的根节点
        y = ufs.find(v2)    # 查找边的另一个端点的根节点
        
        # 如果两个根节点不同,说明不在一个集合内
        if x != y:
            result += val   # 边加入生成树
            ufs.join(x, y)  # 合并两个节点
            
    return result
    

if __name__ == '__main__':
    v, e = map(int, input().split())
    edges = []
    
    # 读取每条边的信息
    for _ in range(e):
        v1, v2, val = map(int, input().split())
        edges.append((v1, v2, val))
        
    result = kruskal(v, edges)
    
    print(result)
"""
记录边
"""
class UnionFindSet:
    def __init__(self, n):
        self.parent = list(range(n))
        
    def find(self, u):
        if self.parent[u] != u:
            self.parent[u] = self.find(self.parent[u])
        return self.parent[u]
        
    def isSame(self, u, v):
        u = self.find(u)
        v = self.find(v)
        return u == v
        
    def join(self, u, v):
        root_u = self.find(u)
        root_v = self.find(v)
        if root_u != root_v:
            self.parent[root_v] = root_u

def kruskal(v, edges):
    edges.sort(key=lambda edge: edge[2])
    
    ufs = UnionFindSet(v + 1)
    
    result_val = 0      # 记录最小生成树的值
    resule_edge = []    # 记录最小生成树的边
    
    for edge in edges:
        v1, v2, val = edge 
        x = ufs.find(v1) 
        y = ufs.find(v2)
        
        if x != y:
            result_val += val
            ufs.join(x, y)
            resule_edge.append(edge)    # 保存最小生成树的边
            
    return result_val, resule_edge
    

if __name__ == '__main__':
    v, e = map(int, input().split())
    edges = []
    
    for _ in range(e):
        v1, v2, val = map(int, input().split())
        edges.append((v1, v2, val))
        
    result_val, resule_edge = kruskal(v, edges)
    
    print(result_val)
    
    for edge in resule_edge:
        print(f"{edge[0]} - {edge[1]} : {edge[2]}")

总结

区别:prim 算法是维护点的集合,kruskal 算法是维护边的集合

选择:
如果一个图节点多,但是边相对少,选择 kruskal 算法更优;
若果一个图边多,节点少,选择 prim 算法更优。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值