47

So I have a problem that I want to use depth first search to solve, returning the first path that DFS finds. Here is my (incomplete) DFS function:

    start = problem.getStartState()
    stack = Stack()
    visited = []
    stack.push(start)
    if problem.isGoalState(problem.getStartState):
        return something
    while stack:
        parent = stack.pop()
        if parent in visited: continue
        if problem.isGoalState(parent):
            return something
        visited.append(parent)
        children = problem.getSuccessors(parent)
        for child in children:
            stack.push(child[0])

The startState and goalState variables are simply a tuple of x, y coordinates. problem is a class with a variety of methods. The important ones here are getSuccessors (which returns the children of a given state in the form of a list of 3 item tuples. for this part of the problem though, only the first element of the tuple, (child[0]), which returns the state of the child in x, y coordinates, is important) and isGoalState (which provides the x, y coordinates of the goal state).

So I THINK (difficult to test at this point), that this function, given proper implementation of everything else, will return once it has reached a goal state. Please let me know if I am missing something. My biggest issue, though, is WHAT to return. I want it to output a list of all of the states it takes to get to the goal state, in order from the beginning to the end. It doesn't seem like simply returning my stack will do the trick, since the stack will include many unvisited children. Nor will my visited list yield anything useful, since it is conceivable I could reach dead ends, have to backtrack, but still have the dead-end tuples in the visited list. How would I go about getting the list I desire?

user1427661
  • 11,158
  • 28
  • 90
  • 132
  • 21
    Best of luck with your Pacman AI homework ;) http://ai.berkeley.edu/project_overview.html – SMKS Sep 01 '16 at 16:02

4 Answers4

53

You are right - you cannot simply return the stack, it indeed contains a lot of unvisited nodes.

However, by maintaining a map (dictionary): map:Vertex->Vertex such that parentMap[v] = the vertex we used to discover v, you can get your path.

The modification you will need to do is pretty much in the for loop:

    for child in children:
        stack.push(child[0])
        parentMap[child] = parent #this line was added

Later on, when you found your target, you can get the path from the source to the target (pseudo code):

curr = target
while (curr != None):
  print curr
  curr = parentMap[curr]

Note that the order will be reversed, it can be solved by pushing all elements to a stack and then print.

I once answered a similar (though not identical IMO) question regarding finding the actual path in BFS in this thread

Another solution is to use a recursive version of DFS rather then iterative+stack, and once a target is found, print all current nodes in the recursion back up - but this solution requires a redesign of the algorithm to a recursive one.


P.S. Note that DFS might fail to find a path to the target (even if maintaining a visited set) if the graph contains an infinite branch.
If you want a complete (always finds a solution if one exists) and optimal (finds shortest path) algorithm - you might want to use BFS or Iterative Deepening DFS or even A* Algorithm if you have some heuristic function

Community
  • 1
  • 1
amit
  • 175,853
  • 27
  • 231
  • 333
  • Nice solution . . . For getting the order right, would something like this work as well?: – user1427661 Oct 12 '12 at 18:06
  • while cure != None: newList.append(curr) curr = parentMap[curr]. And then print(newList.reverse())? Sorry for the lack of line breaks, can't do them in comments. – user1427661 Oct 12 '12 at 18:08
  • @user1427661: (1) You are sometimes using `cure` and sometimes `curr` - it should be one of them. (2) Yes, it should work (3) Read my edit regarding the choice of DFS as your algorithm - you should be aware of its disadvantages. – amit Oct 12 '12 at 18:10
  • 2
    @amit This actually doesn't work if nodes have edges that are double sides (i.e. Edge(u,v) and Edge(v,u) exist). – 14wml Mar 15 '17 at 04:43
  • @amit For example, n = 6, s (start node) = 3, t (end node) = 0 Adjacency list: [(0 = 1 2 3 4 5 ), (1 = 0 2 3), (2 = 0 3 4 5), (3 = 0 1 2 4 5), (4 = 0 1 2 3 5), (5 = 0 3)]. Parent Map: [(0, 5) (1,3), (2,3), (3,5), (4,3), (5,3)]. As you can see 3 maps to 5 but 5 maps to 3, creating an infinite loop while trying to print the path – 14wml Mar 15 '17 at 04:46
  • 5
    `for child in children` instead of doing `parentMap[child] = parent` you need to check if the child has already been added to the parentMap (i.e. `if(!parentMap.containsKey(child)){parentMap.put(child, parent);}`) – 14wml Mar 15 '17 at 05:20
  • I believe there can't be a cycle or overlapping included in the chain. Therefore we need to check if the child already in the visited before adding it. – windmaomao Nov 22 '20 at 04:19
32

Not specific to your problem, but you can tweak this code and apply it to different scenarios, in fact, you can make the stack also hold the path.

Example:

     A
   /    \
  C      B
  \     / \
   \    D E
    \    /
       F
       
graph = {'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])}

def dfs_paths(graph, start, goal):
    stack = [(start, [start])]
    visited = set()
    while stack:
        (vertex, path) = stack.pop()
        if vertex not in visited:
            if vertex == goal:
                return path
            visited.add(vertex)
            for neighbor in graph[vertex]:
                stack.append((neighbor, path + [neighbor]))

print (dfs_paths(graph, 'A', 'F'))   #['A', 'B', 'E', 'F']
ggorlen
  • 44,755
  • 7
  • 76
  • 106
XueYu
  • 2,355
  • 29
  • 33
5

this link should help you alot ... It is a lengthy article that talks extensively about a DFS search that returns a path... and I feel it is better than any answer I or anyone else can post

http://www.python.org/doc/essays/graphs/

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • ok its more generally talking about graphs ... but if you examine it it is indeed DFS ... and it is ducktyped so they can copy/paste and view results unlike OP ... – Joran Beasley Oct 12 '12 at 17:58
  • (Deleted previous too harsh comment, this is the rephrase) The OP asks about a *specific* issue of DFS - answering with a generic link does not seem like an answer to me. At least direct him to the relevant part in the article that answers the question in your opinion. – amit Oct 12 '12 at 17:59
  • the whole thing is about returning paths ... which was the question, ...that said your answer tailored to his specific question certainly answers the OP ... but this article is still probably good reading material for him... it provides a very nice graph exploration function that is clear and concise ... – Joran Beasley Oct 12 '12 at 18:38
1

I just implemented something similar in PHP.

The basic idea behind follows as: Why should I maintain another stack, when there is the call stack, which in every point of the execution reflects the path taken from the entry point. When the algorithm reaches the goal, you simply need to travel back on the current call stack, which results in reading the path taken in backwards. Here is the modified algorithm. Note the return immediately sections.

/**
 * Depth-first path
 * 
 * @param Node $node        Currently evaluated node of the graph
 * @param Node $goal        The node we want to find
 *
 * @return The path as an array of Nodes, or false if there was no mach.
 */
function depthFirstPath (Node $node, Node $goal)
{
    // mark node as visited
    $node->visited = true;

    // If the goal is found, return immediately
    if ($node == $goal) {
        return array($node);
    }

    foreach ($node->outgoing as $edge) {

        // We inspect the neighbours which are not yet visited
        if (!$edge->outgoing->visited) {

            $result = $this->depthFirstPath($edge->outgoing, $goal);

            // If the goal is found, return immediately
            if ($result) {
                // Insert the current node to the beginning of the result set
                array_unshift($result, $node);
                return $result;
            }
        }
    }

    return false;
}
raam86
  • 6,785
  • 2
  • 31
  • 46