Python中Prim的MST算法

时间:2019-11-12 18:23:55

标签: python minimum-spanning-tree prims-algorithm

我正在尝试为包含城市作为顶点的图形实现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语句时,它会无限循环,并且我无法确切找出原因或解决方法。我意识到我可能完全错误且效率低下地实施了该算法,因此,我非常感谢您提供有关从何处去/如何做不同以及为什么的任何提示或建议。先感谢您!!

1 个答案:

答案 0 :(得分:0)

您的实施存在三个主要问题:

  1. 您的图存储为有向图。 Prim 算法不能应用于有向图 (reference)。如果您真的想处理有向图,链接问题中的评论提供了有关如何计算有向图 (reference) 的 MST 等价的信息。

    这也与你的算法卡住的原因有关:它探索所有可以通过起始顶点的有向边到达的顶点。图中的顶点较多,但在固定方向遍历边时,无法从起始顶点到达。因此,该算法无法生长中间树以包含更多顶点并卡住。

    但是,我假设您实际上希望图形是无向的。在这种情况下,您可以例如通过在相反方向复制每条边来计算图形的对称闭包。或者,您可以调整算法以检查出边和入边。

  2. 在 Prim 的算法中,每次迭代都会向中间树添加一条边。这是通过从图中的其余部分选择连接中间树和顶点的最小权重边来完成的。但是,在您的代码中,您改为按权重对中间树中每个顶点的传出边进行排序,并添加指向尚未包含在中间树中的顶点的所有边。这会给你错误的结果。考虑以下示例:

    enter image description here

    假设我们从 a 开始。您的方法按权重对 a 的边进行排序,并按该顺序添加它们。因此,它将边 a-ba-c 添加到中间树。图的所有顶点现在都包含在中间树中,因此算法终止。然而,树 b-a-c 不是最小生成树。

    因此,您应该从 a 中选择最小边,即 a-b。然后,您应该从中间树中的任何顶点搜索最小权重边。这将是边 b-c。然后将此边添加到中间树,最终产生 MST a-b-c

  3. 循环条件 A != V 永远不会满足,即使 A 包含图的每个顶点。这是因为 A 是一个列表,而 Vdict.keys() 的结果,它不是一个列表,而是一个 view object。视图对象是可迭代的,键将按照插入的顺序给出,但它永远不会评估为等于列表,即使列表包含相同的项目。

    即使您将 V 转换为列表(例如使用 V = list(dict.keys()),这也是不够的,因为列表仅在包含相同顺序的相同项目时才相等。但是,您不保证算法会按照将键插入字典的顺序将顶点插入到 A 中。

    因此,您应该对循环条件使用其他方法。一种可能的解决方案是将 VA 初始化为 set,即使用 V = set(dict.keys())A = set(['HOUSTON'])。这将允许您保留 A != V 作为循环条件。或者,您可以保留 A 一个列表,但只检查每次迭代中 A 的大小是否等于 V 的大小。由于算法仅在顶点尚未包含在 A 中时才插入到 A 中,因此当 len(A) == len(dict) 时,A 包含所有顶点。

这是固定代码的示例。它首先采用输入图的对称闭包。作为循环条件,它检查 A 的大小是否不等于顶点总数。在循环中,它计算连接 A 中的顶点与不在 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 森林)。