python - Prim 的 Python 中的 MST 算法
问题描述
我正在尝试为由城市作为顶点组成的图实现 Prim 算法,但我被卡住了。任何建议将不胜感激。
我正在从 txt 文件中读取数据并尝试获取分数(总距离)的输出和元组中的边列表。例如,从休斯顿开始,第一条边将是 ('HOUSTON', 'SAN ANTONIO')。
我使用具有相邻顶点及其距离的字典实现了图形/树,如下所示:
{'HOUSTON': [('LUBBOCK', '535'), ('MIDLAND/ODESSA', '494'), ('MISSION/MCALLEN/EDINBURG', '346'), ('SAN ANTONIO', '197')],
'HARLINGEN/SAN BENITO': [('HOUSTON', '329')],
'SAN ANTONIO': [],
'WACO': [],
'ABILENE': [('DALLAS/FORT WORTH', '181')],
'LUBBOCK': [('MIDLAND/ODESSA', '137')],
'COLLEGE STATION/BRYAN': [('DALLAS/FORT WORTH', '181'), ('HOUSTON', '97')],
'MISSION/MCALLEN/EDINBURG': [],
'AMARILLO': [('DALLAS/FORT WORTH', '361'), ('HOUSTON', '596')],
'EL PASO': [('HOUSTON', '730'), ('SAN ANTONIO', '548')],
'DALLAS/FORT WORTH': [('EL PASO', '617'), ('HOUSTON', '238'), ('KILLEEN', '154'), ('LAREDO', '429'), ('LONGVIEW', '128'), ('LUBBOCK', '322'), ('MIDLAND/ODESSA', '347'), ('MISSION/MCALLEN/EDINBURG', '506'), ('SAN ANGELO', '252'), ('SAN ANTONIO', '271'), ('WACO', '91'), ('WICHITA FALLS', '141')],
'KILLEEN': [],
'SAN ANGELO': [],
'MIDLAND/ODESSA': [],
'WICHITA FALLS': [],
'CORPUS CHRISTI': [('DALLAS/FORT WORTH', '377'), ('HOUSTON', '207')],
'AUSTIN': [('DALLAS/FORT WORTH', '192'), ('EL PASO', '573'), ('HOUSTON', '162')],
'LONGVIEW': [],
'BROWNSVILLE': [('DALLAS/FORT WORTH', '550'), ('HOUSTON', '355')],
'LAREDO': [('SAN ANTONIO', '157')]}
这是我到目前为止所拥有的:
import csv
import operator
def prim(file_path):
with open(file_path) as csv_file:
csv_reader = csv.reader(csv_file, delimiter = "\t")
dict = {}
for row in csv_reader:
if row[0] == 'City':
continue
if row[0] in dict:
dict[row[0]].append((row[1],row[3]))
if row[1] not in dict:
dict[row[1]] = []
else:
dict[row[0]] = [(row[1], row[3])]
V = dict.keys()
A = ['HOUSTON']
score = 0 # score result
E = [] # tuple result
while A != V:
for x in A:
dict[x].sort(key=lambda x: x[1])
for y in dict[x]:
if y[0] in V and y[0] not in A:
A.append(y[0])
E.append((x, y[0]))
score += int(y[1])
break
break
break
print("Edges:")
print(E)
print("Score:")
print(score)
prim("Texas.txt")
由于最后一个 break 语句,这给出了正确的第一个边缘,但是当我删除 break 语句时,它会无限循环,我无法确切地弄清楚为什么或如何修复它。我意识到我可能完全错误且低效地实施了这个算法,所以我非常感谢任何关于从这里去哪里/做什么不同以及为什么做的提示或建议。先感谢您!!
解决方案
您的实施存在三个主要问题:
您的图存储为有向图。Prim 的算法不能应用于有向图(参考)。如果您真的想处理有向图,链接问题中的评论提供了有关如何计算有向图的 MST 等效项的信息(参考)。
这也与您的算法卡住的原因有关:它探索通过起始顶点的有向边可到达的所有顶点。图中的顶点比较多,但是沿固定方向遍历边时,无法从起始顶点到达。因此,该算法不能增长中间树以包含更多的顶点并卡住。
但是,我假设您实际上希望图形是无向的。在这种情况下,您可以通过反向复制每条边来计算图形的对称闭包。或者,您可以调整您的算法来检查传出和传入边缘。
在 Prim 算法中,每次迭代都会向中间树添加一条边。这是通过选择连接中间树与图其余部分的顶点的最小权重边来完成的。但是,在您的代码中,您改为按其权重对中间树中每个顶点的传出边进行排序,并添加所有指向尚未包含在中间树中的顶点的边。这会给你不正确的结果。考虑以下示例:
假设我们从 开始
a
。您的方法按权重对边缘进行排序,a
然后按顺序添加它们。因此,它将边a-b
和添加a-c
到中间树。图的所有顶点现在都包含在中间树中,因此算法终止。但是,该树b-a-c
不是最小生成树。因此,
a
您应该从中选择最小边,即a-b
. 然后,您应该从中间树的任何顶点中搜索最小权重边。这将是边缘b-c
。然后,您将此边添加到中间树,最终生成 MSTa-b-c
。A != V
即使A
包含图形的每个顶点,循环条件也永远不会得到满足。这是因为A
是一个列表,V
是 的结果dict.keys()
,它不是一个列表,而是一个视图对象。视图对象是可迭代的,并且键将按照插入时的顺序给出,但它永远不会评估为等于列表,即使列表包含相同的项目。即使您将
V
变成一个列表(例如,使用V = list(dict.keys())
,这还不够,因为列表只有在它们包含相同顺序的相同项目时才相等。但是,您不能保证算法会将顶点插入A
到与插入字典的键的顺序相同。因此,您应该对循环条件使用其他方法。一种可能的解决方案是将
V
and初始化A
为set
s,即使用V = set(dict.keys())
andA = set(['HOUSTON'])
。这将允许您保持A != V
循环条件。或者你可以保留A
一个列表,但只检查每次迭代的大小A
是否等于 的大小V
。A
由于该算法仅在顶点尚未包含在 中时才插入A
,当 时len(A) == len(dict)
,它遵循A
包含所有顶点。
这是固定代码的示例。它首先采用输入图的对称闭包。作为循环条件,它检查 的大小A
是否不等于顶点总数。在循环中,它计算连接顶点 inA
和顶点 not in的最小权重边A
:
# Compute symmetric closure
for v, edges in dict.items():
for neighbor, weight in edges:
edges_neighbor = dict[neighbor]
if (v, weight) not in edges_neighbor:
dict[neighbor].append((v, weight))
A = ['HOUSTON']
score = 0
E = []
while len(A) != len(dict):
# Prepend vertex to each (target,weight) pair in dict[x]
prepend_source = lambda x: map(lambda y: (x, *y), dict[x])
edges = itertools.chain.from_iterable(map(prepend_source, A))
# Keep only edges where the target is not in A
edges_filtered = filter(lambda edge: edge[1] not in A, edges)
# Select the minimum edge
edge_min = min(edges_filtered, key=lambda x: int(x[2]))
A.append(edge_min[1])
E.append((edge_min[0], edge_min[1]))
score += int(edge_min[2])
请注意,此实现仍然假设图是连接的。如果要处理断开连接的图,则必须对图的每个连接组件应用此过程(导致 MST 森林)。
推荐阅读
- validation - 如何使自定义约束在 Grails 3.3.10 中工作?
- c# - 如何在 MVC4 的 if 条件中检查具有值“”的 String [] 数组?
- typescript - 为什么打字稿将联合中的属性标记为不存在?
- python - 如何在 python 中建模这个 MILP 问题?
- node.js - 在“pretest”脚本中执行两个 Sequelize 命令时出现“错误:验证错误”消息
- python - 如何使用 CNN 模型 Keras 解决错误?
- java - 如何通过 Spring Boot 查询区域内的点?
- angular - 在 Angular 8 中初始化嵌套对象的有效方法
- python - “依赖于安装的默认”目录布局背后的想法是什么?
- c++ - 在 C++ 中,struct 的内存结构是如何默认排列的?