6

我想实现一个函数,它从有向循环图 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).

有什么想法可以在可接受的时间内运行吗?

4

3 回答 3

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 个节点的全连接图中所有节点的所有路径。这是您任务的上限,所以我认为您不想这样做。我想你想要别的东西。

于 2011-10-20T11:55:39.213 回答
2

编辑: 好的,我现在明白了,您想从有向图中的顶点找到所有简单路径。因此,正如您所意识到的,带有回溯的深度优先搜索将是合适的。一般的想法是去一个邻居,然后去另一个(不是你去过的),一直走,直到你走到死胡同。然后回溯到您所在的最后一个顶点并选择一个不同的邻居,等等。您需要正确处理这些繁琐的部分,但这不应该太难。例如,在每个步骤中,您都需要根据您之前是否已经访问过它们来标记顶点“已探索”或“未探索”。性能应该不是问题,正确实施的算法可能需要 O(n^2) 时间。所以我不知道你做错了什么,也许你访问的邻居太多了?例如

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

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

我很想知道您是否需要它来完成特定任务,或者只是出于兴趣而尝试对此进行编程。如果是后者,您只需要对小的、稀疏连接的图感到满意。

于 2011-10-20T12:37:48.633 回答
1

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

你可以在这里看到一个例子:Some Erlang Matrix Functions,在 maximise_assignment 函数中(从今天的第 191 行开始的评论)。同样,那里的底层算法相当幼稚和蛮力,但是对于许多形式的矩阵,并行化可以很好地加速它。

我过去使用过类似的方法来查找图中哈密顿路径的数量。

于 2011-10-22T02:05:11.960 回答