我想实现一个函数,它从有向循环图 G 中的源顶点 V 找到所有可能顶点的所有可能路径。
现在性能无所谓,我只是想了解一下算法。我已经阅读了深度优先搜索算法的定义,但我没有完全理解要做什么。
我没有任何完整的代码可以在这里提供,因为我不知道如何:
- 存储结果(连同 A->B->C-> 我们还应该存储 A->B 和 A->B->C);
- 表示图(有向图?元组列表?);
- 使用多少递归(使用每个相邻的顶点?)。
如何在 Erlang 的有向循环图中从一个给定的源顶点找到所有可能的路径?
UPD:根据到目前为止的答案,我必须重新定义图定义:它是一个非循环图。我知道如果我的递归函数遇到一个循环,它就是一个无限循环。为了避免这种情况,我可以检查当前顶点是否在结果路径的列表中 - 如果是,我停止遍历并返回路径。
UPD2:感谢发人深省的评论!是的,我需要找到从一个源顶点到所有其他顶点没有循环的所有简单路径。
在这样的图表中:
对于源顶点 A,算法应该找到以下路径:
- 甲,乙
- A,B,C
- A B C D
- 广告
- 甲、丁、丙
- 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).
有什么想法可以在可接受的时间内运行吗?