2
votes

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.

2
You really just need a pair of parameters to represent the input Visited and the output one. Take the set union of the output Visited sets of your neighbors plus yourself and that's the output Visited. - Daniel Lyons
@DanielLyons I still don't get how I would apply this to all my neighbors for each nodes, since I have to accumulate all the visited nodes from my neighbors before backtracking to any upper nodes. - Louis-Vincent Boudreault

2 Answers

3
votes

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
votes

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.