How to implement depth first search for graph with a non-recursive approach

前端 未结 13 1754
情话喂你
情话喂你 2020-11-28 22:00

I have spent lots of time on this issue. However, I can only find solutions with non-recursive methods for a tree: Non recursive for tree, or a recursive method for the grap

相关标签:
13条回答
  • 2020-11-28 22:17

    I think you need to use a visited[n] boolean array to check if the current node is visited or not earlier.

    0 讨论(0)
  • 2020-11-28 22:18

    A DFS without recursion is basically the same as BFS - but use a stack instead of a queue as the data structure.

    The thread Iterative DFS vs Recursive DFS and different elements order handles with both approaches and the difference between them (and there is! you will not traverse the nodes in the same order!)

    The algorithm for the iterative approach is basically:

    DFS(source):
      s <- new stack
      visited <- {} // empty set
      s.push(source)
      while (s is not empty):
        current <- s.pop()
        if (current is in visited):
            continue
        visited.add(current)
        // do something with current
        for each node v such that (current,v) is an edge:
            s.push(v)
    
    0 讨论(0)
  • 2020-11-28 22:18

    Many people will say that non-recursive DFS is just BFS with a stack rather than a queue. That's not accurate, let me explain a bit more.

    Recursive DFS

    Recursive DFS uses the call stack to keep state, meaning you do not manage a separate stack yourself.

    However, for a large graph, recursive DFS (or any recursive function that is) may result in a deep recursion, which can crash your problem with a stack overflow (not this website, the real thing).

    Non-recursive DFS

    DFS is not the same as BFS. It has a different space utilization, but if you implement it just like BFS, but using a stack rather than a queue, you will use more space than non-recursive DFS.

    Why more space?

    Consider this:

    // From non-recursive "DFS"
    for (auto i&: adjacent) {
        if (!visited(i)) {
            stack.push(i);
        }
    }
    

    And compare it with this:

    // From recursive DFS
    for (auto i&: adjacent) {
        if (!visited(i)) {
            dfs(i);
        }
    }
    

    In the first piece of code you are putting all the adjacent nodes in the stack before iterating to the next adjacent vertex and that has a space cost. If the graph is large it can make a significant difference.

    What to do then?

    If you decide to solve the space problem by iterating over the adjacency list again after popping the stack, that's going to add time complexity cost.

    One solution is to add items to the stack one by one, as you visit them. To achieve this you can save an iterator in the stack to resume the iteration after popping.

    Lazy way

    In C/C++, a lazy approach is to compile your program with a larger stack size and increase stack size via ulimit, but that's really lousy. In Java you can set the stack size as a JVM parameter.

    0 讨论(0)
  • 2020-11-28 22:20

    Using Stack and implementing as done by the call stack in the recursion process-

    The Idea is to push a vertex in the stack, and then push its vertex adjacent to it which is stored in a adjacency list at the index of the vertex and then continue this process until we cannot move further in the graph, now if we cannot move ahead in the graph then we will remove the vertex which is currently on the top of the stack as it is unable to take us on any vertex which is unvisited.

    Now, using stack we take care of the point that the vertex is only removed from the stack when all the vertices that can be explored from the current vertex have been visited, which was being done by the recursion process automatically.

    for ex -

    See the example graph here.

    ( 0 ( 1 ( 2 ( 4 4 ) 2 ) ( 3 3 ) 1 ) 0 ) ( 6 ( 5 5 ) ( 7 7 ) 6 )

    The above parenthesis show the order in which the vertex is added on the stack and removed from the stack, so a parenthesis for a vertex is closed only when all the vertices that can be visited from it have been done.

    (Here I have used the Adjacency List representation and implemented as a vector of list (vector > AdjList) by using C++ STL)

    void DFSUsingStack() {
        /// we keep a check of the vertices visited, the vector is set to false for all vertices initially.
        vector<bool> visited(AdjList.size(), false);
    
        stack<int> st;
    
        for(int i=0 ; i<AdjList.size() ; i++){
            if(visited[i] == true){
                continue;
            }
            st.push(i);
            cout << i << '\n';
            visited[i] = true;
            while(!st.empty()){
                int curr = st.top();
                for(list<int> :: iterator it = AdjList[curr].begin() ; it != AdjList[curr].end() ; it++){
                    if(visited[*it] == false){
                        st.push(*it);
                        cout << (*it) << '\n';
                        visited[*it] = true;
                        break;
                    }
                }
                /// We can move ahead from current only if a new vertex has been added on the top of the stack.
                if(st.top() != curr){
                    continue;
                }
                st.pop();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 22:20

    The following Java Code will be handy:-

    private void DFS(int v,boolean[] visited){
        visited[v]=true;
        Stack<Integer> S = new Stack<Integer>();
        S.push(v);
        while(!S.isEmpty()){
            int v1=S.pop();     
            System.out.println(adjLists.get(v1).name);
            for(Neighbor nbr=adjLists.get(v1).adjList; nbr != null; nbr=nbr.next){
                 if (!visited[nbr.VertexNum]){
                     visited[nbr.VertexNum]=true;
                     S.push(nbr.VertexNum);
                 }
            }
        }
    }
    public void dfs() {
        boolean[] visited = new boolean[adjLists.size()];
        for (int v=0; v < visited.length; v++) {
            if (!visited[v])/*This condition is for Unconnected Vertices*/ {
    
                System.out.println("\nSTARTING AT " + adjLists.get(v).name);
                DFS(v, visited);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 22:23

    This is not an answer, but an extended comment, showing the application of the algorithm in @amit's answer to the graph in the current version of the question, assuming 1 is the start node and its neighbors are pushed in the order 2, 4, 3:

                   1
                 / |  \
                4  |   2
                   3 /
    
    Actions            Stack             Visited
    =======            =====             =======
    push 1             [1]               {}
    pop and visit 1    []                {1}
     push 2, 4, 3      [2, 4, 3]         {1}
    pop and visit 3    [2, 4]            {1, 3}
     push 1, 2         [2, 4, 1, 2]      {1, 3}
    pop and visit 2    [2, 4, 1]         {1, 3, 2}
     push 1, 3         [2, 4, 1, 1, 3]   {1, 3, 2}
    pop 3 (visited)    [2, 4, 1, 1]      {1, 3, 2}
    pop 1 (visited)    [2, 4, 1]         {1, 3, 2}
    pop 1 (visited)    [2, 4]            {1, 3, 2}
    pop and visit 4    [2]               {1, 3, 2, 4}
      push 1           [2, 1]            {1, 3, 2, 4}
    pop 1 (visited)    [2]               {1, 3, 2, 4}
    pop 2 (visited)    []                {1, 3, 2, 4}
    

    Thus applying the algorithm pushing 1's neighbors in the order 2, 4, 3 results in visit order 1, 3, 2, 4. Regardless of the push order for 1's neighbors, 2 and 3 will be adjacent in the visit order because whichever is visited first will push the other, which is not yet visited, as well as 1 which has been visited.

    0 讨论(0)
提交回复
热议问题