9.3 最小生成树算法

接下来我们看最小生成树问题,和下一节的最短路径问题容易混淆。

最小生成树的概念


(1)一个带权值的图:网。并需要最小成本,就是用n-1条边把n个顶点连接起来,且连接起来的权值最小。
(2)我们把构造联通网的最小代价生成树称为最小生成树

产生最小生成树必须解决下边两个问题:(1) 尽可能选取权值小的边,但不能构成回路;(2) 选取n-1条恰当的边以连通n个顶点。

最小生成树的算法主要有Kruskal算法和Prim算法,他们都是贪心算法的应用。

最小生成树的应用

用带权值的图表示一群城市及城市间的距离,那么最小生成树可以理解为该城市群中修建铁路、公路的总成本。最小生成树的应用基本类似这样,而最短路径用于两个城市之间的一些成本的计算。

最小生成树的算法

下面我们以下图为例,撸一遍最小生成树的算法。

对于这幅图,我们给的已知条件如下。

n = 9
edge = [(0,1,10), (0,5,11), (1,2,18),(1,6,16), (1,8,12), (2,3,22),(2,8,8), (3,4,20), (3,6,24), (3,7,16), (3,8,21), (4,5,26), (4,7,7), (5,6,17), (6,7,19)]

(1) Prim算法

过程描述:Prim算法始终以顶点为主导,并且起始点的选择是任意的。

从起始点到其他点选择最小权值边,然后以此边两个顶点分别再找最小权值的边,同样已经间接连接的边跳过。

时间复杂度是O(n2),适用于求边稠密连通网的最小生成树。

结合图和代码的注释,学习一个。解法的关键是lowcost的更新,然后贪心的选择最优的边及其顶点,思路还是很简单的。

def miniSpanTree_prim(n,edge):
    from collections import defaultdict
    # 根据边 建图
    graph = defaultdict(dict)
    for e in edge:
        graph[e[0]][e[1]] = e[2]
        graph[e[1]][e[0]] = e[2]
    # 注意这两个工具, lowcost存储当前情况下(初始为v0) 所有顶点的最小权重 
    # prenodes 为所有顶点最小权重对应的上一个顶点
    lowcost = [0 for i in range(n)]
    prenodes = [0 for i in range(n)]
    # 初始化lowcost : 所有顶点和v0的距离 没有边的话 可以设置为一个不可能的值
    for i in range(1, n):
        lowcost[i] = graph[0].get(i, -1)
    cost = 0  # 最小生成树总成本
    for i in range(1, n):  # i代表迭代次数,无意义
        minw = 999
        j = 1
        val = 0
        # 遍历所有顶点 找出lowcost中 权重最小的,和本身的距离为0 不考虑,没有边时为-1 不考虑
        while j < n:
            if lowcost[j] > 0 and lowcost[j] < minw:
                minw = lowcost[j]
                val = j
            j += 1
        print(prenodes[val],"->", val, minw) # 此时确定了当前情况下选择的下一个顶点val即边的权重
        lowcost[val] = 0  # lowcost中设置为0 表示val已被选择
        cost += minw
        # 遍历一遍 val顶点的所有边 更新(!!)其他顶点的最小权重 和 prenodes
        for k in range(1, n):
            if k not in graph[val].keys():
                continue
            if lowcost[k] != 0:
                if lowcost[k] > 0 and graph[val][k] < lowcost[k] or lowcost[k] == -1:
                    lowcost[k] = graph[val][k]
                    prenodes[k] = val
            
    return cost

我们以0点为初始,运行代码结果如下:

miniSpanTree_prim(n, edge)
'''
0 -> 1 10
0 -> 5 11
1 -> 8 12
8 -> 2 8
1 -> 6 16
6 -> 7 19
7 -> 4 7
7 -> 3 16
99

'''

(2) Kruskal算法

过程描述:始终以边为主导地位,先选择权值最小的边,总是选择当前可用最小权值边,并且每次判断两点之间是否已经间接连通,如果已经间接连通,则跳过此边。

时间复杂度是O(n*logn),适用于求边稀疏连通网的最小生成树。

还是要结合图和代码,自己画一遍中间变量 尤其是prenodes,理解算法的精髓。该算法核心就是理解prenodes用于判断是否成环,以及他的更新,,代码是Kruskal的核心,还可以根据需要输出更多信息。

def find_parent(x, info):
    while info[x] > 0:
        x = info[x]
    return x
def miniSpanTree_kruskal(n, edge):
    prenodes = [0 for i in range(n)]  # 核心变量
    edge.sort(key=lambda x: x[2]) # 边按照权重排序
    print(edge)
    cost = 0
    for e in edge:
        # 查找 这条边两顶点的父节点 下面列出了 begin和end在前几次循环过程的值
        begin = find_parent(e[0], prenodes)  # 4 2 0 1 5 3
        end = find_parent(e[1], prenodes)    # 7 8 1 5 8 7
        # 说明没有回路
        if begin != end:
            prenodes[begin] = end  # 此时将该条边加入最小生成树 更新prenodes
            print(e[0], "->", e[1], e[2])
            cost += e[2]
    return cost

代码运行结果如下:

miniSpanTree_kruskal(n, edge)
'''
[(4, 7, 7), (2, 8, 8), (0, 1, 10), (0, 5, 11), (1, 8, 12), (1, 6, 16), (3, 7, 16), (5, 6, 17), (1, 2, 18), (6, 7, 19), (3, 4, 20), (3, 8, 21), (2, 3, 22), (3, 6, 24), (4, 5, 26)]
4 -> 7 7
2 -> 8 8
0 -> 1 10
0 -> 5 11
1 -> 8 12
1 -> 6 16
3 -> 7 16
6 -> 7 19
99
'''

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值