在Erlang中查找有向循环图中一个顶点的所有可能路径

时间:2011-10-20 10:38:54

标签: graph erlang depth-first-search directed-graph

我想实现一个函数,该函数在有向循环图G中找到源顶点V中所有可能顶点的所有可能路径。

现在表现无关紧要,我只是想了解算法。我已经阅读了深度优先搜索算法的定义,但我还没有完全理解该怎么做。

我在这里没有提供任何完整的代码,因为我不知道如何:

  • 存储结果(连同A-> B-> C->我们还应该存储A-> B和A-> B-> C);
  • 代表图表(有向图?元组列表?);
  • 要使用多少递归(处理每个相邻的顶点?)。

如何在Erlang中的有向循环图中找到一个给定源顶点的所有可能路径?

UPD:根据目前为止的答案,我必须重新定义图形定义:它是一个非非循环图形。我知道如果我的递归函数遇到一个循环,它就是一个无限循环。为了避免这种情况,我可以检查当前顶点是否在结果路径的列表中 - 如果是,我将停止遍历并返回路径。


UPD2:感谢发人深省的评论!是的,我需要找到所有没有从一个源顶点到所有其他源的循环的简单路径。

在这样的图表中:

Non-acyclic graph

使用源顶点A算法应找到以下路径:

  • A,B
  • A,B,C
  • A,B,C,d
  • A,d
  • A,d,C
  • A,d,C,B

下面的代码完成了这项工作,但是对于有超过20个顶点的图形它是无法使用的(我猜它是递归错误的 - 占用太多内存,永远不会结束):

dfs(Graph,Source) ->
    ?DBG("Started to traverse graph~n", []),
            Neighbours = digraph:out_neighbours(Graph,Source),
    ?DBG("Entering recursion for source vertex ~w~n", [Source]),
            dfs(Neighbours,[Source],[],Graph,Source),
ok.


dfs([],Paths,Result,_Graph,Source) ->
    ?DBG("There are no more neighbours left for vertex ~w~n", [Source]),
    Result;

dfs([Neighbour|Other_neighbours],Paths,Result,Graph,Source) ->
    ?DBG("///The neighbour to check is ~w, other neighbours are: ~w~n",[Neighbour,Other_neighbours]),
    ?DBG("***Current result: ~w~n",[Result]),
    New_result = relax_neighbours(Neighbour,Paths,Result,Graph,Source),

        dfs(Other_neighbours,Paths,New_result,Graph,Source).


relax_neighbours(Neighbour,Paths,Result,Graph,Source) ->
     case lists:member(Neighbour,Paths) of 
        false ->
            ?DBG("Found an unvisited neighbour ~w, path is: ~w~n",[Neighbour,Paths]),
            Neighbours = digraph:out_neighbours(Graph,Neighbour),
            ?DBG("The neighbours of the unvisited vertex ~w are ~w, path is:
                ~w~n",[Neighbour,Neighbours,[Neighbour|Paths]]),
                dfs(Neighbours,[Neighbour|Paths],Result,Graph,Source);
            true ->
                [Paths|Result]

        end.

UPD3:

问题在于,常规深度优先搜索算法将首先进入路径之一:(A,B,C,D)或(A,D,C,B),并且永远不会进入第二条路径。

在任何一种情况下,它都是唯一的路径 - 例如,当常规DFS从(A,B,C,D)回溯时,它会返回到A并检查是否访问了D(A的第二个邻居) 。由于常规DFS为每个顶点维护一个全局状态,因此D将处于“已访问”状态。

所以,我们必须引入一个递归依赖状态 - 如果我们从(A,B,C,D)回溯到A,我们应该在结果列表中有(A,B,C,D)我们应该在算法的最开始将D标记为未访问。

我试图将解决方案优化为尾递归算法,但算法的运行时间仍然不可行 - 遍历16个顶点的小图表需要大约4秒钟,每个顶点有3个边缘:

dfs(Graph,Source) ->
    ?DBG("Started to traverse graph~n", []),
            Neighbours = digraph:out_neighbours(Graph,Source),
    ?DBG("Entering recursion for source vertex ~w~n", [Source]),
    Result = ets:new(resulting_paths, [bag]),
Root = Source,
            dfs(Neighbours,[Source],Result,Graph,Source,[],Root).


dfs([],Paths,Result,_Graph,Source,_,_) ->
    ?DBG("There are no more neighbours left for vertex ~w, paths are ~w, result is ~w~n", [Source,Paths,Result]),
    Result;

dfs([Neighbour|Other_neighbours],Paths,Result,Graph,Source,Recursion_list,Root) ->
    ?DBG("~w *Current source is ~w~n",[Recursion_list,Source]),
    ?DBG("~w Checking neighbour _~w_ of _~w_, other neighbours are: ~w~n",[Recursion_list,Neighbour,Source,Other_neighbours]),



?    DBG("~w Ready to check for visited: ~w~n",[Recursion_list,Neighbour]),

 case lists:member(Neighbour,Paths) of 
        false ->
            ?DBG("~w Found an unvisited neighbour ~w, path is: ~w~n",[Recursion_list,Neighbour,Paths]),
New_paths = [Neighbour|Paths],
?DBG("~w Added neighbour to paths: ~w~n",[Recursion_list,New_paths]),
ets:insert(Result,{Root,Paths}),

            Neighbours = digraph:out_neighbours(Graph,Neighbour),
            ?DBG("~w The neighbours of the unvisited vertex ~w are ~w, path is: ~w, recursion:~n",[Recursion_list,Neighbour,Neighbours,[Neighbour|Paths]]),
                dfs(Neighbours,New_paths,Result,Graph,Neighbour,[[[]]|Recursion_list],Root);
            true -> 
            ?DBG("~w The neighbour ~w is: already visited, paths: ~w, backtracking to other neighbours:~n",[Recursion_list,Neighbour,Paths]),
ets:insert(Result,{Root,Paths})

end,

        dfs(Other_neighbours,Paths,Result,Graph,Source,Recursion_list,Root).

在可接受的时间内运行此操作的想法是什么?

3 个答案:

答案 0 :(得分:2)

我不明白问题。如果我有图G =(V,E)=({A,B},{(A,B),(B,A)}),从A到B有无限路径{[A,B],[ A,B,A,B],[A,B,A,B,A,B],......}。如何找到循环图中任何顶点的所有可能路径?

编辑:

您是否尝试过计算或猜测某些图形的可能路径增长?如果您有完全连接的图表,您将获得

  • 2 - 1
  • 3 - 4
  • 4 - 15
  • 5 - 64
  • 6 - 325
  • 7 - 1956
  • 8 - 13699
  • 9 - 109600
  • 10 - 986409
  • 11 - 9864100
  • 12 - 108505111
  • 13 - 1302061344
  • 14 - 16926797485
  • 15 - 236975164804
  • 16 - 3554627472075
  • 17 - 56874039553216
  • 18 - 966858672404689
  • 19 - 17403456103284420
  • 20 - 330665665962403999

您确定要查找所有节点的所有路径吗?这意味着如果您在一秒钟内计算一个百万路径,则需要10750年来计算到具有20个节点的完全连接图中所有节点的所有路径。它是你的任务的上限,所以我认为你不想这样做。我想你想要别的东西。

答案 1 :(得分:2)

修改 好的,我现在明白了,你想要从有向图中的顶点找到所有简单路径。因此,正如您已经意识到的那样,使用回溯的深度优先搜索将是合适的。一般的想法是去邻居,然后去另一个(不是你去过的那个),并继续前进,直到你走到尽头。然后回溯到你所在的最后一个顶点并选择一个不同的邻居等。 你需要得到正确的位,但它不应该太难。例如。在每一步,你需要标记顶点'探索'或'未探索',这取决于你以前是否已经访问它们。性能应该不是问题,正确实现的算法应该花费O(n ^ 2)时间。所以我不知道你做错了什么,也许你正在拜访太多的邻居?例如。也许你正在重新访问你已经访问过的邻居,然后绕圈或其他东西。

我还没有真正阅读过您的程序,但深度优先搜索的Wiki页面有一个简短的伪代码程序,您可以尝试使用您的语言进行复制。将图形存储为邻接列表以使其更容易。

修改 是的,对不起,你是对的,标准的DFS搜索不能正常工作,你需要稍微调整它,以便重新访问之前访问过的顶点。因此,您可以访问除已存储在当前路径中的顶点之外的任何顶点。 这当然意味着我的运行时间完全错误,算法的复杂性将通过屋顶。如果图的平均复杂度为d + 1,那么将会有大约d * d * d * ... * d = d ^ n个可能的路径。 因此,即使每个顶点只有3个邻居,当你获得20个以上的顶点时,仍然会有相当多的路径。 真的没办法,因为如果你想让你的程序输出所有可能的路径,那么你真的必须输出所有d ^ n。

我很想知道您是否需要针对特定​​任务执行此操作,或者只是想尝试对此进行编程。如果是后者,你只需要对小的,稀疏连接的图表感到满意。

答案 2 :(得分:1)

通过任何方式都不是改进的算法解决方案,但您通常可以通过生成多个工作线程来提高性能,这可能是每个第一级节点一个,然后聚合结果。这通常可以相对容易地改善幼稚蛮力算法。

你可以在这里看到一个例子:Some Erlang Matrix Functions,在maximise_assignment函数中(从今天开始的第191行开始的评论)。同样,基础算法存在相当天真和暴力,但并行化可以很好地加速许多形式的矩阵。

我过去曾使用类似的方法在图表中找到汉密尔顿路径的数量。