【问题标题】:Check if exists any path, in DI-Graph检查是否存在任何路径,在 DI-Graph
【发布时间】:2020-11-04 14:24:22
【问题描述】:

如果我有一个有向图,如何检查所有节点对 (a,b) 是否创建了路径?

例子:

输入:

v1 v2
v5 v6
v2 v3
v3 v4
v4 v5
v0 v1

而且我需要检查是否存在至少一条通过该图的路径,而不是多次访问每个节点。

我已经尝试过回溯,但最大的输入需要几个小时...

具体例子:

在输入时我有边:

{m,a}, {a,c}, {a,m} 

我必须检查,如果有路径,在这种情况下它会返回 True,因为存在

{a,m} -> {m, a} -> {a,c}

【问题讨论】:

  • 你的问题非常模棱两可。你能澄清一下你想做什么吗?
  • 输入时我有边:{m,a}, {a,c}, {a,m} 我必须检查是否有路径,在这种情况下它将返回 True , 因为存在 {a,m} -> {m, a} -> {a,c}
  • 从您的列表中选择一对;寻找可以将其连接到的另一对;如果存在这样的一对,则弹出两个对,推送它们的连接;如果不存在这样的对,但至少有两个不同的对,则不存在路径。
  • 您检查过networkx 中的simple path 函数吗?
  • 你的例子不满足每个节点只被访问一次的要求:a被访问两次。

标签: python graph graph-theory


【解决方案1】:

一种相对幼稚的二次算法

从您的路径列表中弹出一个路径。在列表中弹出另一个路径以将其连接起来。将连接的路径推回列表中。如果在任何时候我们都找不到另一条路径来连接它,这意味着答案是否定的,所有节点对都不会组合成一条路径,所以我们返回None

def combine_into_one_path(list_of_paths):
  path = list_of_paths.pop()
  while list_of_paths:
    path2 = pop_adjacent_path(list_of_paths, path[0], path[-1])
    if path2 is None:
      return None
    elif path[-1] == path2[0]:
      path = path[:-1] + path2
    elif path2[-1] == path[0]:
      path = path2[:-1] + path
    else:
      assert(False)
  return path

def pop_adjacent_path(list_of_paths, a, b):
  for i,p in enumerate(list_of_paths):
    if p[0] in (a, b) or p[-1] in (a,b):
      return list_of_paths.pop(i)
  return None

print(combine_into_one_path([[1, 2], [5, 6], [2, 3], [3, 4], [4, 5], [0, 1]]))
# [0, 1, 2, 3, 4, 5, 6]

print(combine_into_one_path([[1, 2], [5, 6], [2, 3], [3, 7], [4, 5], [0, 1]]))
# None

该算法在路径数上是二次的,因为combine_into_one_path 中的while 循环对列表中的每个路径进行一次迭代,并且函数pop_adjacent_path 也对列表进行迭代。

请注意,此代码不会检查节点是否唯一;例如,[v1, v2, v3, v2, v4, v1, v5] 将被视为有效路径。您可以在 combine_into_one_path 的最终返回之前添加检查,以确保路径中的每个元素都是唯一的。

线性化

算法变慢的原因是必须遍历整个列表以找到一对节点来组合我们当前的路径。避免这种情况的一种方法是将配对存储在字典中,这样我们就可以回答“配对是否以a 结尾?”的问题。和“一对是否以b 开头?”在恒定的时间内。

def combine_into_one_path(list_of_paths):
  path = list_of_paths.pop()
  forwards = {p[0]:p for p in list_of_paths}
  backwards = {p[-1]:p for p in list_of_paths}
  while forwards:
    if path[-1] in forwards:
      p2 = forwards[path[-1]]
      del forwards[path[-1]]
      del backwards[p2[-1]]
      path = path[:-1] + p2
    elif path[0] in backwards:
      p2 = backwards[path[0]]
      del backwards[path[0]]
      del forwards[p2[0]]
      path = p2[:-1] + path
    else:
      return None
    print('\npath     =', path)
    print('forwards =', forwards)
    print('backwards=', backwards)
  return path

print(combine_into_one_path(['manta', 'alcats', 'random']))
# randomantalcats

这几乎是相同的算法,但我们将函数 pop_adjacent_path 替换为字典检查,它是常数时间而不是线性时间。

只是为了了解算法的工作原理:

list_of_paths = [[1, 2], [5, 6], [3, 4], [4, 5], [0, 1], [2, 3]]

path     = [2, 3]
forwards = {1: [1, 2], 5: [5, 6], 3: [3, 4], 4: [4, 5], 0: [0, 1]}
backwards= {2: [1, 2], 6: [5, 6], 4: [3, 4], 5: [4, 5], 1: [0, 1]}

path     = [2, 3, 4]
forwards = {1: [1, 2], 5: [5, 6], 4: [4, 5], 0: [0, 1]}
backwards= {2: [1, 2], 6: [5, 6], 5: [4, 5], 1: [0, 1]}

path     = [2, 3, 4, 5]
forwards = {1: [1, 2], 5: [5, 6], 0: [0, 1]}
backwards= {2: [1, 2], 6: [5, 6], 1: [0, 1]}

path     = [2, 3, 4, 5, 6]
forwards = {1: [1, 2], 0: [0, 1]}
backwards= {2: [1, 2], 1: [0, 1]}

path     = [1, 2, 3, 4, 5, 6]
forwards = {0: [0, 1]}
backwards= {1: [0, 1]}

path     = [0, 1, 2, 3, 4, 5, 6]
forwards = {}
backwards= {}

【讨论】:

  • 返回:del backwards[p2[-1]] KeyError: 100
  • @MartinPoláček 在哪个输入上?
  • @MartinPoláček 这个单词列表有几个单词以相同的字母开头或以相同的字母结尾!!这根本不像你的问题,而且会是一个更难的问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-18
  • 2020-05-08
  • 1970-01-01
  • 2019-04-03
  • 2020-02-28
  • 2011-02-04
相关资源
最近更新 更多