【问题标题】:Generate all possible paths from a list of tuples从元组列表中生成所有可能的路径
【发布时间】:2019-05-16 00:21:40
【问题描述】:

给定一个元组列表,我需要从这个列表中找到所有唯一路径:

输入:[('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]
输出:[['a','b','c','d','e','f','g'],['a','b','c','g','i']] (2 条可能的唯一路径)

如果元组的第二个元素与另一个元组的第一个元素匹配,则两个元组可以连接,即:一个元组是(_,a),另一个元组类似于(a,_)

这个问题已经在那里提出了:Getting Unique Paths from list of tuple 但解决方案是在 haskell 中实现的(我对这种语言一无所知)。

但是您知道在 Python 中是否有一种有效的方法可以做到这一点?
我知道库itertools 有许多高效的内置函数来处理类似的事情,但我对此不太熟悉。

【问题讨论】:

标签: python


【解决方案1】:

您希望在图表中找到所有simple paths

Python 有一个很棒的图形处理库:networkx。你可以用几行代码来解决你的问题:

import networkx as nx

a = [('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]

# Create graph
G = nx.Graph()
# Fill graph with data
G.add_edges_from(a)

# Get all simple paths from node 'a' to node 'i'
list(nx.all_simple_paths(G, 'a', 'i'))

会还给你:

[['a', 'b', 'c', 'g', 'i'], ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i']]


如果你想要所有可能的路径,只需用它替换最后一行:

for start in G.nodes:
    for end in G.nodes:
        if start != end:
            print(list(nx.all_simple_paths(G, start, end)))

【讨论】:

  • 非常感谢 :) 但是在您的解决方案中,您假设 'a' 和 'i' 必然是您可能路径的开始和结束(在我的问题中,我对我的路径如何开始没有任何假设最后,我只知道应该如何构建它们)。但我想我可以通过简单地生成所有(开始/结束)组合然后将你的方法应用于每个组合来生成所有可能的路径(虽然可能很长)
  • 更新了答案。
  • 请注意,在所有节点的嵌套循环中调用 nx.all_simple_paths 的更新答案将解决方案的平均时间复杂度变为 O(n ^ 3)
【解决方案2】:

您可以构建一个字典,将每个父节点映射到一个已连接子节点的列表,这样您就可以以 O(n) 的平均时间复杂度递归地生成每个父节点的路径:

def get_paths(parent, mapping):
    if parent not in mapping:
        yield [parent]
        return
    for child in mapping[parent]:
        for path in get_paths(child, mapping):
            yield [parent, *path]

edges = [('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]
parents = set()
children = set()
mapping = {}
for a, b in edges:
    mapping.setdefault(a, []).append(b)
    parents.add(a)
    children.add(b)
print([path for parent in parents - children for path in get_paths(parent, mapping)])

这个输出:

[['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i'], ['a', 'b', 'c', 'g', 'i']]

【讨论】:

    【解决方案3】:

    您可以将递归与生成器一起使用:

    d = [('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]
    def get_paths(start, c = []):
       r = [b for a, b in d if a == start]
       if r:
         for i in r:
            yield from get_paths(i, c+[i])
       else:
         yield c
    
    print(list(get_paths('a', ['a'])))
    

    输出:

    [['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i'], ['a', 'b', 'c', 'g', 'i']]
    

    【讨论】:

    • 请注意,必须在每次递归调用中遍历所有节点,使得该解决方案的平均时间复杂度 O(n ^ 2)。它还需要提前知道起始节点,这不是这个问题的输入。
    • @blhsing 您如何获得O(n^2)O(n) 的答案?您的字典查找是O(n),但在for 循环中进行递归调用,最坏的结果是>= O(2^n)(我的也是)。
    • 字典查找平均花费 O(1),而不是 O(n),而您在所有节点中线性搜索给定父节点[b for a, b in d if a == start] 花费 O(n)。递归调用线性遍历所有节点,因此我的解决方案将花费 O(n) 而您的解决方案花费 O(n ^ 2)。然而,另一个因素是发散路径的数量,当父节点有多个子节点时会发生这种情况,如果这个数字很大,那么我的解决方案将花费 O(n x m),而你的将花费 O(n ^ 2 x m),其中n 是节点数,m 是分叉路径数。
    • @blhsing 我的答案包含对d 的额外传递,但加上递归调用的线性时间复杂度,结果不是O(n)+O(n) => O(n) 吗?循环外的额外线性传递不会增加n的功率。
    • 不,您在每个递归调用中迭代所有节点的长度,并且递归调用至少执行节点数的次数,因此导致@987654334 @ 次n 步骤。在 dict 中查找节点消除了在每个递归调用中迭代所有节点的需要,因此我的解决方案的 O(n) 复杂性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 2014-10-22
    • 2017-08-13
    相关资源
    最近更新 更多