最小生成树

目录

一、概念

二、如何构建最小生成树

三、克鲁斯卡尔python代码实现


一、概念

1、Without any cycle,不能形成闭环;

2、Connect all the vertices,必须连接图结构中的每个顶点,意思是每个顶点,都能通过某种方法连接起来;

3、N Vertices, N-1 Edges,N个顶点数,边的个数是N-1;

最小的概念:

把边对应的全值数相加,称为全值和,不同的树对应不同的全值和。最小生成树对应的就是全值树对应的那棵树。

二、如何构建最小生成树

1、克鲁斯卡尔算法(Kruskal)

2、普里姆算法(Prim)

       

既然目标是全值和达到最小值,所以每一步都优先选择权值最小的边,这一点两个算法相同。但两个算法在达到这一目标的方法不同。克鲁斯卡尔是直接选择权值最小的边,而普里姆算法是从顶点出发,选择与顶点相邻、权值最小的边。

1、克鲁斯卡尔算法(Kruskal)

                        图1

       将图中所有的边都取出放入一个列表中,并按照权值由小到大的排列。按列表的顺序,将每一条边回贴到图中,每回贴一条边,就判断每条边的状态,看看是否能形成环,如果没有形成环,这条边就会被保留,如果形成环,这条边就会被放弃,继续判断列表中的下一条边。直到选择了N-1条边,这时最小生成树就完成。

如何判断欲加入的一条边是否与生成树中已保留的边形成回路呢?有一个方法是:并查集(标号法)(合并并查找集合)(不是一家人,不进一家门)。

并查集(下方第二个链接,6:56):

S1:将图各边按照权值进行排序;并且给每个顶点都加上一个序号(表示该顶点属于标号所示的联通分量);接下来就要按权值大小的顺序选边,然后看这个边对应的两个顶点,看这两个顶点的序号是否一样。

S2:选中处于同一条边上的两个顶点(按照权值排序从小到大选择),如果两个顶点的标号值不同,则将标号修改为同一个类的标号值(把大的标号改为小的标号);

S3:如果标号值相同,表示回路出现,需要把这一条边舍去,选择下一条权重对应的边。直到所有顶点的标号值一直为止,有n-1条边为止。

2、普里姆算法(Prim)

同样优先选择权值小的边,但是从顶点出发,优先选择连接两个顶点集合的所有边中权值最小的。

图2

这两个顶点集合,一个是已选顶点集合(已经确定最小生成树连接方式的顶点,图2左边绿色区域),一个时是未选顶点集合(未确定顶点连接方式的所有顶点构成的未选顶点集)。普里姆算法就是在连接这两个集合之间的所有边中连接最小的边。如图2,接下来连接蓝色和绿色的线就是7-6之间权重为1的这条,随后图2变为图3.

 图3

具体实现过程,就是通过不断更新三个列表信息。第一个列表,存储该顶点是否已被选取的信息,如果该顶点以确定了最小生成树的连接方式,则为T,否则为F,列表中所有为T的顶点,就构成了前面所说的已选顶点,初始状态下,所有顶点都为F;第二个列表,存储某一时刻连接已选顶点连接未选顶点中,所有边中权值最小的那一条边,初始状态下都为无穷大(inf);最小权值边,两端顶点信息就存储在第三个列表中,由于两个顶点确定一条边,所以初始状态都为-1,表示不存在的一条线。

图4

具体操作过程如下,直接看后面括号部分吧:

以顶点0为起点(确定一个起点,执行下面的三个操作),

1、Update ,更新信息,当 已选顶点中每加入一个新的顶点,更新所有与该顶点相连的顶点信息,如果连接边的权值小于对应顶点列表(minDist)中的值,则更新列表值为该边的权值,同时更新(parent)记录改边,反之则不变(找到与此时起点相连的点,在表中更新它的信息,第二列表写两点间线的权值,第三列表写此时起点对应的数字)

2、Scan,扫描minDist,找到最小值对应的顶点(找到该起点连接其他点对应的最小权值边,通过扫描第二列表)

 3、Add,将找到的最小权值边的点,与此时对应的点在图中连接起来,创建最小生成树的一条边(将第二列表扫描到的点与此时起点连接起来,作图就连接成了一条最小生成树,这三步走完,这个点对应的数列就会变化,第一行变作T,第二行变作-,第三行变作此时起点对应的数值;当然,变化了后的扫描,当然只在第一行为F的地方扫啦。接着,用这个被选中的顶点,继续第一步重复(?也有更复杂的情况,比如图5,此时这个被选中的顶点为8,但是8明显不能作下一个起点,所以就莫名变成了3为起点))。

 

图5

三、克鲁斯卡尔python代码实现

# 找这个顶点的标号
def find(x, pres): # x是这条边的一个顶点,pres是它的标号集

    root = pres[x]  
    return root

# 更改顶点的标点
def change(x, y, pres):
    """
    合并两个元素(合并两个集合)
    :return: None
    """
    h1, h2 = find(x, pres), find(y, pres)
    # 当两个元素不是同一组的时候才合并
    if h1 != h2:
        if pres[h1] < pres[h2]:  # 把标号往小的改
            pres[h2] = pres[h1]
        else:
            pres[h1] = pres[h2]
  


def kruskal(n, edges):
    """
    kruskal算法
    :param n: 结点数
    :param edges: 带权边集
    :return: 构成最小生成树的边集
    """
    # 初始化:标点集pres = [0, 1, 2, 3, 4, 5];  
    pres = [e for e in range(n)]  
    # 边从小到大排序
    edges = sorted(edges, key=lambda x: x[-1]) # sorted用于排序
    print("edges = ")
    print(edges)
    mst_edges, num = [], 0
    
    for edge in edges:
        if find(edge[0], pres) != find(edge[1], pres):
            mst_edges.append(edge)  # 如果两个顶点的标号不相同,那么就把这条边加入树中
            change(edge[0], edge[1], pres) # 更改顶点的标点
            num += 1
        else:
            continue
        if num == n-1:
            break
    return mst_edges

# !!数据 采用mst图,需要更改
edges = [
    [0, 1, 6], 
    [0, 2, 1],
    [0, 3, 5],
    [2, 1, 5],
    [2, 3, 5],
    [2, 4, 5],
    [2, 5, 4],
    [1, 4, 3],
    [4, 5, 6],
    [5, 3, 2]
]


# !!结点数(顶点数),需要更改
n = 6
mst_edges = kruskal(n, edges)
print('edges:')
for e in mst_edges:
    print(e)
print('Total cost of MST:', sum([e[-1] for e in mst_edges]))
print('Maximum cost of MST:', max([e[-1] for e in mst_edges]))


# std print
#
# edges:
# [0, 2, 1]
# [5, 3, 2]
# [1, 4, 3]
# [2, 5, 4]
# [2, 1, 5]
# Total cost of MST: 15
# Maximum cost of MST: 5

学习对象:

【最小生成树(Kruskal(克鲁斯卡尔)和Prim(普里姆))算法动画演示】 https://www.bilibili.com/video/BV1Eb41177d1/?share_source=copy_web&vd_source=98fbab4e0ff3ef4e18cd477db479634d

【图的最小生成树克鲁斯卡尔(Kruskal)算法-理论+代码】 https://www.bilibili.com/video/BV1d34y1R7xA/?share_source=copy_web&vd_source=98fbab4e0ff3ef4e18cd477db479634d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值