【问题标题】:Recursive function to calculate simple path from start to end in a graph递归函数计算图中从开始到结束的简单路径
【发布时间】:2019-06-13 13:21:08
【问题描述】:

我写了一个递归函数来在图中找到一个简单的路径(只存在一个路径),给定图边列表作为元组。例如:

edges = [(0, 10),(2, 16),(4, 5),(6, 24),(7, 6),(8, 23),(9, 25),(10, 14),(11, 1),(12, 19),(13, 22),(14, 15),(15, 11),(16, 7),(17, 21),(18, 13),(19, 17),(20, 8),(21, 3),(22, 20),(23, 12),(24, 9),(25, 18)]

def get_path(sol, start, end):
    out = []
    for i,j in sol:
        if i == start:
            out.append(i)
            out += get_path(sol, j, end)
        if j == end:
            out.append(j)
    return out

但是,如果我无法在返回列表中正确获取结束节点。如果我删除第 7 行和第 8 行,最后一个节点将不会出现在我的解决方案中,而且如果我在代码中有这些行,我会在我的解决方案中获得多个端节点。举个例子:

get_path(edges, 2, 3)
path = [2, 16, 7, 11, 15, 14, 10, 22, 13, 18, 25, 21, 17, 19, 12, 9, 24, 6, 8, 20, 23]

我们可以看到结束节点是3,它应该是列表的最后一个元素。我非常感谢您的意见。谢谢

【问题讨论】:

  • 我会放弃这种方法并使用 Dijkstra 的算法。如果您的输入图很大,您可能会发现自己遇到了堆栈问题。

标签: python recursion graph


【解决方案1】:

您可以为此使用networkX。您可以从边缘定义网络并获得两个给定节点之间的简单路径。我正在使用nx.shortest_simple_paths,但您可以使用all_simple_paths 来获取它们:

import networkx as nx

G=nx.Graph()
G.add_edges_from(edges)
next(nx.shortest_simple_paths(G, 2, 3))
# [2, 16, 7, 6, 24, 9, 25, 18, 13, 22, 20, 8, 23, 12, 19, 17, 21, 3]

【讨论】:

  • 非常感谢您的回复。我对此很清楚。但我正在尝试尽量减少程序中的依赖关系。
  • 我明白了。我强烈建议将 networkX 用于网络分析任务以获得更好的性能。 @MohammadAbdollahi
【解决方案2】:

代码中的每个递归调用都会有一次迭代,其中 j == end 为真,因此您确实会多次添加它:与递归深度一样多。

如果将结束条件放在循环之外会更好,如下所示:

if start == end:
    return [start]

因此,只需稍加修改,您的代码将如下所示:

def get_path(sol, start, end):
    if start == end:
        return [start]
    out = []
    for i,j in sol:
        if i == start:
            out.append(i)
            out += get_path(sol, j, end)
    return out

其他几点说明:

  • 您可以一次性执行if块中的两个列表操作:

       out += [i] + get_path(sol, j, end)
    
  • 遍历每个中的完整列表是非常低效的 递归调用。最好先建一个字典,keyed 由起始编号。这样你就不必反复寻找 对应的边。

  • 您的代码似乎希望该图是一个链,没有任何 您有 2 个或更多传出边的节点。如果发生这种情况,if i == start: 块将执行多次,从而导致输出尴尬。这种情况也不太难涵盖

代码:

from collections import defaultdict 

def get_path(sol, start, end):
    # Transform to dict
    d = defaultdict(list)
    for i,j in sol:
        d[i] += [j]

    def recur(start):
        if start == end:
            return [start]
        for nxt in d[start]:
            path = recur(nxt)
            if path is not None: 
                return [start] + path
    return recur(start)

edges = [(0, 10),(2, 16),(4, 5),(6, 24),(7, 6),(8, 23),(9, 25),(10, 14),
         (11, 1),(12, 19),(13, 22),(14, 15),(15, 11),(16, 7),(17, 21),
         (18, 13),(19, 17),(20, 8),(21, 3),(22, 20),(23, 12),(24, 9),(25, 18)]

path = get_path(edges, 2, 3)
print (path)

循环图

你的问题是关于无环图,因为你写的:

只有一条路

但是对于任何来这里寻找循环图解决方案的人来说,上述解决方案需要扩展,以免陷入循环。这可以通过将节点标记为已访问来完成:

from collections import defaultdict 

def get_path(sol, start, end):
    # Transform to dict
    d = defaultdict(list)
    for i,j in sol:
        d[i] += [j]

    visited = set()
    def recur(start):
        if start == end:
            return [start]
        visited.add(start)
        for nxt in d[start]:
            if nxt not in visited:
                path = recur(nxt)
                if path is not None: 
                    return [start] + path
        visited.remove(start)
    return recur(start)

edges =  [(0,1),(1,0),(0,2),(2,0),(2,3),(3,2)]

path = get_path(edges, 2, 3)
print (path)

请注意,这两种解决方案都不能保证找到的路径是最短的。但是如果知道只有一条路径,那就是一种内存效率高的算法。

如果有多个路径,则深度优先搜索在找到路径时无法停止搜索,因为可能仍会找到更短的路径。另一方面,如果您使用广度优先搜索,那么您可以在找到路径后立即停止。

【讨论】:

  • 对于图边 = [(0,1),(1,0),(0,2), (2,0), (2,3),(3,2)]你的代码有一个循环。我认为您的代码仅适用于没有循环的有向图。
  • 是的,但是 OP 写道“只存在一条路径”,所以他们的问题是关于无环图。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-31
  • 2017-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多