Prolog:如何从给定节点中找到所有可到达的节点?

时间:2018-04-23 04:34:00

标签: prolog

鉴于事实:

edges(a,[b,c]). 
edges(b,[d]).
edges(c,[a]).
edges(d,[e]).

现在,我可以编写以下谓词:

find(F, L) :- 
    edges(F, Nodes) -> 
    findall([X|Y], (member(X, Nodes), find(X, Y)), L);
    L = [].

没有循环时工作正常,例如find(b,L)。给了我和我。但是当循环存在时,它不起作用。那么如何修改我的代码来处理循环呢?例如find(c,L)将输出a,b,c,d,e以及find(a,L)。 任何帮助都表示赞赏。

3 个答案:

答案 0 :(得分:2)

您可以选择使用累加器来跟踪您访问过的节点。为此,您需要一个列表作为附加参数。由于此列表在搜索开始时为空,因此您始终使用[]调用谓词,因此您也可以使用调用谓词隐藏它,我们可以将其称为start_dest/2:< / p>

start_dest(S,D) :-
   dif(S,D),                % start and destination nodes are different
   start_dest_(S,D,[]).     % actual relation called with empty accumulator

第一个目标dif/2仅用于防止起始节点和目标节点相同的解决方案。如果您想允许此类解决方案,请删除该目标。实际关系将通过逐节点遍历图来搜索可到达的节点。你可以区分两种情况。

  1. 如果两个节点相等,则找到可能的目标节点。

  2. 如果节点不同,则必须是您当前所在节点的邻接列表中的中间节点。到目前为止,搜索中不得访问当前节点(以避免循环)。必须有从中间节点到目的地的路径,并且当前节点不得出现在该路径中,因此必须将其添加到访问节点列表中。

  3. 你可以在Prolog中表达这两个案例,如下:

    start_dest_(D,D,_Visited).        % case 1: destination found
    start_dest_(S,D,Visited) :-       % case 2:
       maplist(dif(S),Visited),       % S has not been visited yet
       edges(S,Reachable),            % Reachable is the adjacence list
       member(X,Reachable),           % that has to contain the intermediate node X
       start_dest_(X,D,[S|Visited]).  % there has to be a path from X to D that
                                      % does not include S
    

    您的示例查询会产生所需的结果:

    ?- start_dest(b,N).
    N = d ;
    N = e ;
    false.
    
    ?- start_dest(c,N).
    N = a ;
    N = b ;
    N = d ;
    N = e ;
    false.
    

    如果删除dif(S,D)中的第一个目标(start_dest/2),则会获得其他解决方案。这对应于每个节点都可以从自身访问的视图。

    ?- start_dest(b,N).
    N = b ;
    N = d ;
    N = e ;
    false.
    

    请注意,此谓词可用于所有方向,例如哪些节点可以e到达?

    ?- start_dest(S,e).
    S = a ;
    S = b ;
    S = c ;
    S = d ;
    false.
    

    或最常见的查询:哪些节点可以从任何节点访问?

    ?- start_dest(S,D).
    S = a,
    D = b ;
    S = a,
    D = d ;
    S = a,
    D = e ;
    S = a,
    D = c ;
    S = b,
    D = d ;
    S = b,
    D = e ;
    S = c,
    D = a ;
    S = c,
    D = b ;
    S = c,
    D = d ;
    S = c,
    D = e ;
    S = d,
    D = e ;
    false.
    

    与谓词find/2相反,start_dest/2一次为您提供一个可达节点。如果要获取列表中的所有可访问节点,可以像findall/3中一样使用bagof/3setof/3find/2等谓词,例如:

    ?- bagof(N, start_dest(b,N), Reachable).
    Reachable = [d, e].
    
    ?- bagof(N, start_dest(c,N), Reachable).
    Reachable = [a, b, d, e].
    

    如果您打算始终搜索所有可到达的节点但又不想一直查询bagof/3,则可以编写一个调用谓词,如:

    reachable_from(Reachable,Start) :-
       bagof(N, start_dest(Start,N), Reachable).
    
    ?- reachable_from(Reachable,Start).
    Reachable = [b, d, e, c],
    Start = a ;
    Reachable = [d, e],
    Start = b ;
    Reachable = [a, b, d, e],
    Start = c ;
    Reachable = [e],
    Start = d.
    

答案 1 :(得分:0)

这是一种可能性:

% we get a list of all edges
get_all_edges(Edges) :-
   bagof(edges(X,Y), edges(X,Y), Edges).

% main predicate
find(F, L) :-
    get_all_edges(Edges),
    find(Edges, F, Out),
    % the result you get is for example  [[a, [b, [d, [e|e], [e]]], [c]]]
    flatten(Out, FOut),
    list_to_set(FOut, L).

% no more edges, work is done
find([], L, L).

find(Edges, F, L) :-
     % we get the good nodes
    select(edges(F, Nodes), Edges, Rest)
    -> findall([X|Y], (member(X, Nodes), find(Rest, X, Y)), L)
    ;  L = [].

结果:

?- find(c, L).
L = [a, b, d, e, c].

答案 2 :(得分:0)

在您学会了基本方法后,请查看libraries提供的内容:

?- findall(V-U,(edges(V,Us),member(U,Us)),Es),
   vertices_edges_to_ugraph([],Es,G),
   reachable(a,G,Rs).
Es = [a-b, a-c, b-d, c-a, d-e],
G = [a-[b, c], b-[d], c-[a], d-[e], e-[]],
Rs = [a, b, c, d, e].

您可能想直接从edge / 2转到ugraph格式,但最好使用预定义的功能(即vertices_edges_to_ugraph / 3)