12.1 Prim算法

最小生成树问题

  最小生成树问题是针对无向图的,事实上在计算机里存储的无向图,都是双向图。只有连通图才有生成树,图的生成树,就是包含了图的所有节点和部分边,但是没有环的子图。因为这个子图是一棵树,所以叫生成树。
  最小生成树指的是权重和最小的生成树。
  如以下就是一个无向图:
在这里插入图片描述
  下面是它的一棵生成树:
在这里插入图片描述
  但是这棵树毫无疑问不是最小生成树,因为它删除的是环中权重最小的边,最小生成树应该是删除环中最长的边,以下才是最小生成树:
在这里插入图片描述

几个概念

  交叉cross,在我的博文最大流最小割定理里讲到了割的概念,交叉就是边的两端分布在割的两部分,也就是说这个边是割的边界处的连接边。
  尊重respect,一个割尊重一个边的集合指的是这个集合里的所有边,都不与割交叉。
  轻边light edge,割的交叉边里权重最小的边。
  安全边sage edge,这个概念比较复杂。对于一个边的集合A,任意尊重A的割中的轻边就是安全边。

一般算法与Prim算法

  一般算法,就是先从空集合开始构建边集A,然后不断寻找A的安全边,再把安全边加入到A中,直到把图的所有点都包含进去,此时A就是最小生成树。
  Prim算法,就是选择割的时候做了唯一限定。因为一般算法里,割是任意选择的。Prim算法是以边集内包含的点为界限来选择割的。这样就唯一了。我以实际案例举个例子,以下是无向加权图图:
在这里插入图片描述
  以点A为起始点:
在这里插入图片描述
  安全边肯定是指向B喽:
在这里插入图片描述

  这时候安全边有两个,A-C和B-D,按顺序选C吧:
在这里插入图片描述
  下一个安全边明显是CF:
在这里插入图片描述
  下一个安全边是CD和FG,按顺序选CD:
在这里插入图片描述
  再选FG:
在这里插入图片描述
  最后是FE:
在这里插入图片描述

Python代码

# _*_ coding:utf-8
class Edge:

    def __init__(self, to, weight):
        self.__to = to
        self.__weight = weight

    @property
    def to(self):
        return self.__to

    @to.setter
    def to(self, to):
        self.__to = to

    @property
    def weight(self):
        return self.__weight

    @weight.setter
    def weight(self, weight):
        self.__weight = weight


WHITE = 0
GRAY = 1
BLACK = 2


class WeightedGraph:
    def __init__(self, vertices, edges):
        self.__vertices = vertices
        self.__edges = edges

    @property
    def vertices(self):
        return self.__vertices

    @vertices.setter
    def vertices(self, value):
        self.__vertices = value

    @property
    def edges(self):
        return self.__edges

    @edges.setter
    def edges(self, value):
        self.__edges = value

    def prim_mst(self):
        # 初始化为空
        a = [[] for _ in self.vertices]
        s = [False for _ in self.__vertices]
        s[0] = True
        s_len = 1
        n = len(self.__vertices)
        pos = ["0,0!", "2,1!", "2,-1!", "4,0!", "6,1!", "6,-1!", "8,0!", ]

        print(f"第{s_len}步", self.to_prim_dot(pos, s, a))
        while s_len < n:
            v, edge = self.safe_edge(s)
            a[v].append(edge)
            # 反向也要添加一下,因为是双向图
            a[edge.to].append(Edge(v, edge.weight))
            s[edge.to] = True
            s_len += 1
            print(f"第{s_len}步", self.to_prim_dot(pos, s, a))
        return a

    def safe_edge(self, s):
        # 寻找安全边
        # 所有的边
        min_weight = None
        sage_edge = None
        start = None
        for v, exists in enumerate(s):
            if not exists:
                continue
            neighbors = self.__edges[v]
            for neighbor in neighbors:
                if s[neighbor.to]:
                    continue
                # 此时是cross
                if min_weight is None or neighbor.weight < min_weight:
                    min_weight = neighbor.weight
                    sage_edge = neighbor
                    start = v
        return start,sage_edge

    def to_dot(self, pos):
        dot_s = 'graph s {\n\tlayout=fdp\n'
        for i, v in enumerate(self.__vertices):
            dot_s += f'\t"{v}"[pos="{pos[i]}"];\n'
        for i, e in enumerate(self.__edges):
            for t in e:
                if t.to < i:
                    continue
                dot_s += f'\t\"{self.__vertices[i]}\"--"{self.__vertices[t.to]}"[label="{t.weight}"];\n'
        dot_s += '}\n'
        return dot_s

    def to_prim_dot(self, pos, s, a):
        dot_s = 'graph s {\n\tlayout=fdp\n'
        for i, v in enumerate(self.__vertices):
            dot_s += f'\t"{v}"[pos="{pos[i]}";'
            if s[i]:
                dot_s += 'style=filled;fillcolor=red;'
            dot_s += '];\n'
        for i, e in enumerate(self.__edges):
            for t in e:
                if t.to < i:
                    continue
                dot_s += f'\t\"{self.__vertices[i]}\"--"{self.__vertices[t.to]}"[label="{t.weight}";'
                for st_edge in a[i]:
                    if st_edge.to == t.to:
                        dot_s += 'color=red;'
                dot_s += '];\n'
        dot_s += '}\n'
        return dot_s

案例的测试类代码

import unittest

from com.youngthing.graph.prim_mst import Edge
from com.youngthing.graph.prim_mst import WeightedGraph


class PrimMstTestCase(unittest.TestCase):
    def test_prim(self):
        # 做一张图
        vertices = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G',]
        # 设计容量
        edges = [
            [Edge(1, 4), Edge(2, 8), ],#a
            [Edge(0, 4), Edge(2, 9), Edge(3, 8), Edge(4, 10)],#B
            [Edge(0, 8), Edge(1, 9), Edge(3, 2), Edge(5, 1)],#C
            [Edge(1, 8), Edge(2, 2), Edge(4, 7), Edge(5, 9)],#D
            [Edge(1, 10), Edge(3, 7), Edge(5, 5), Edge(6, 6)],#E
            [Edge(2, 1), Edge(3, 9), Edge(4, 5), Edge(6, 2)],#f
            [Edge(4, 6), Edge(5, 2)],#g
        ]
        fn = WeightedGraph(vertices, edges)
        pos = ["0,0!", "2,1!", "2,-1!", "4,0!", "6,1!", "6,-1!", "8,0!", ]
        print(fn.to_dot(pos))

        tree = fn.prim_mst()
        print(WeightedGraph(vertices, tree).to_dot(pos))


if __name__ == '__main__':
    unittest.main()


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒过来摸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值