我想实现一个函数,该函数在有向循环图G中找到源顶点V中所有可能顶点的所有可能路径。
现在表现无关紧要,我只是想了解算法。我已经阅读了深度优先搜索算法的定义,但我还没有完全理解该怎么做。
我在这里没有提供任何完整的代码,因为我不知道如何:
如何在Erlang中的有向循环图中找到一个给定源顶点的所有可能路径?
UPD:根据目前为止的答案,我必须重新定义图形定义:它是一个非非循环图形。我知道如果我的递归函数遇到一个循环,它就是一个无限循环。为了避免这种情况,我可以检查当前顶点是否在结果路径的列表中 - 如果是,我将停止遍历并返回路径。
UPD2:感谢发人深省的评论!是的,我需要找到所有没有从一个源顶点到所有其他源的循环的简单路径。
在这样的图表中:
使用源顶点A算法应找到以下路径:
下面的代码完成了这项工作,但是对于有超过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).
在可接受的时间内运行此操作的想法是什么?
答案 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],......}。如何找到循环图中任何顶点的所有可能路径?
您是否尝试过计算或猜测某些图形的可能路径增长?如果您有完全连接的图表,您将获得
您确定要查找所有节点的所有路径吗?这意味着如果您在一秒钟内计算一个百万路径,则需要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行开始的评论)。同样,基础算法存在相当天真和暴力,但并行化可以很好地加速许多形式的矩阵。
我过去曾使用类似的方法在图表中找到汉密尔顿路径的数量。