问题
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