最小生成树问题
最小生成树问题是针对无向图的,事实上在计算机里存储的无向图,都是双向图。只有连通图才有生成树,图的生成树,就是包含了图的所有节点和部分边,但是没有环的子图。因为这个子图是一棵树,所以叫生成树。
最小生成树指的是权重和最小的生成树。
如以下就是一个无向图:
下面是它的一棵生成树:
但是这棵树毫无疑问不是最小生成树,因为它删除的是环中权重最小的边,最小生成树应该是删除环中最长的边,以下才是最小生成树:
几个概念
交叉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()