prolog graph depth first search

为君一笑 提交于 2020-12-13 07:17:05

问题


I've seen other questions about depth first search but my problem is slightly different and I really don't get it. In prolog, I represent my undirected graph like this:

[0-[1,5], 1-[0,2], 2-[1,3], 3-[2,0], 5-[0]]

Which is a set of key-value, where the key represents the node and the list: -[] represents its neighbors.

I don't know how to do a depth first search with this model. I've tried many solution. I want a really basic recursive algorithm like this one:

dfs(v, visited):
  if visited[v] then return
  visited.insert(v)
  foreach neighbor of v:
    dfs(neighbor, visited)

What I can't do in prolog is pass visited as a mutable reference which will be modified by each neighbor for the next neighbor recursively.

Because If I translate it into prolog:

% here Graph contains the entire key-value pairs list,
% Node-Neighbor is the current node with its neighbors.
dfs(Graph, Node-Neighbors, Visited) :-
    \+ member(Node, Visisted),
    % Now here I could get the next neighbor within neighbor, like so:
    member(Node1,Neighbors),
    % Then gets its own neighbors too by:
    member(Node1-Neighbors1, Graph),
    % but here I'm stuck...

I would need some kind of folding where each neighbors call dfs and its result is passed to the next neighbors, but I don't know how to do that...

Thanks.


回答1:


The traditional DFS algorithm involves recursion and a stack which is basically a backtracking mechanism. In Prolog, if you are trying to "accumulate" a list, it can be challenging if the elements you need are obtained on backtracking.

The following code is a very straightforward rendering of the DFS search and will write out the nodes in a DFS search:

dfs(Graph, StartNode) :-
    dfs(Graph, StartNode, []).

dfs(Graph, Node, Visited) :-
    \+ member(Node, Visited),
    member(Node-Neighbors, Graph),
    write(Node), nl,
    member(NextNode, Neighbors),
    dfs(Graph, NextNode, [Node|Visited]).

The two member calls look a little like what you were attempting to do, but not quite the same. Together, they are the key to the DFS traversal for this structure.

Querying this you would get:

| ?- dfs([0-[1,5], 1-[0,2], 2-[1,3], 3-[2,0], 5-[0]], 0).
0
1
2
3
5

yes
| ?-

But, writing out the nodes in the graph isn't particularly useful. We'd like to collect them in a list. The above can be modified so that it becomes a predicate that succeeds for each node along the search path.

dfs(Graph, StartNode, Node) :-
    dfs(Graph, StartNode, [], Node).

dfs(Graph, ThisNode, Visited, Node) :-
    \+  member(ThisNode, Visited),
    (   Node = ThisNode                     % Succeed finding a new node
    ;   member(ThisNode-Neighbors, Graph),  % Or... find the next node
        member(NextNode, Neighbors),
        dfs(Graph, NextNode, [ThisNode|Visited], Node)
    ).

This results in:

| ?- dfs([0-[1,5], 1-[0,2], 2-[1,3], 3-[2,0], 5-[0]], 0, Node).

Node = 0 ? a

Node = 1

Node = 2

Node = 3

Node = 5

no
| ?-

Now you can use findall/3 to get the nodes as a list:

dfs_path(Graph, StartNode, Nodes) :-
    findall(Node, dfs(Graph, StartNode, Node), Nodes).

Now you can write:

| ?- dfs_path([0-[1,5], 1-[0,2], 2-[1,3], 3-[2,0], 5-[0]], 0, Nodes).

Nodes = [0,1,2,3,5]

yes
| ?-



回答2:


Just directly translate your code to Prolog. Don't overthink it; say what you mean:

/* dfs(v, visited):
     if visited[v] then return
     visited.insert(v)
     foreach neighbor of v:
       dfs(neighbor, visited) */

dfs(Graph, V, Visited, Return) :-
   visited(V, Visited),
   return(Graph, V, Visited, Return).

dfs(Graph, V, Visited, Return) :-
   \+ visited(V, Visited),
   appended(Visited, [V], Visited2),
   neighbor(Graph, V, N),
   dfs(Graph, N, Visited2).

The predicates visited/2, return/4, appended/3, neighbor/3 should be straightforward to implement.

Correctness first, efficiency later.



来源:https://stackoverflow.com/questions/50766565/prolog-graph-depth-first-search

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!