如何使用非递归的方法实现深度优先搜索图形

Als*_*ton 31 algorithm graph depth-first-search non-recursive

好吧,我花了很多时间在这个问题上.但是,我只能找到树的递归方法的解决方案:树的非递归,或图的递归方法,图的递归.

许多教程(我不在这里提供这些链接)也没有提供方法.或者教程完全不正确.请帮我.

更新:

这很难描述:

如果我有一个无向图:

               1
             / |  \
            4  |   2
               3 /
Run Code Online (Sandbox Code Playgroud)

1-- 2-- 3 - 1是一个循环.

在这一步: push the neighbors of the popped vertex into the stack

WHAT'S THE ORDER OF THE VERTEXES SHOULD BE PUSHED?

如果推送的顺序是2 4 3,则堆栈中的顶点是:

| |
|3|
|4|
|2|    
 _
Run Code Online (Sandbox Code Playgroud)

弹出节点后,我们得到了结果:1 - > 3 - > 4 - > 2而不是1 - > 3 - > 2 - > 4.

这是不正确的.我应该添加什么条件来阻止这个场景?

ami*_*mit 46

没有递归的DFS与BFS基本相同- 但是使用堆栈而不是队列作为数据结构.

线程Iterative DFS vs Recursive DFS和不同的元素顺序处理两种方法和它们之间的区别(并且有!你不会以相同的顺序遍历节点!)

迭代方法的算法基本上是:

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)
Run Code Online (Sandbox Code Playgroud)

  • @Stallman`visited`跟踪被访问的节点,这样你就不会两次访问同一个节点.至于迭代的顺序,如果你有一个特定的顺序,你可以通过用你的迭代顺序替换每个节点的行`来实现它.+1为一个好的答案! (2认同)
  • 对于空间和时间,此代码为O(n²).考虑一个[完整图表](https://en.wikipedia.org/wiki/Complete_graph)(其中每个顶点都连接到每个其他顶点).对于所有_n_顶点,while循环的每次迭代都会将另一个_n_顶点列表添加到堆栈中,因此总共会有O(n²)次迭代.由于访问顶点的弹出,空间增长有点慢,但它仍然是O(n²),因为它增长为算术系列n +(n-1)+(n-2)+ ... + 2 + 1.尝试[此示例代码](https://repl.it/Eq81/2). (2认同)

Pat*_*han 16

这不是一个答案,而是一个扩展的评论,显示算法在@ amit对问题当前版本中图形的回答中的应用,假设1是起始节点,其邻居按顺序推送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}
Run Code Online (Sandbox Code Playgroud)

因此,应用按顺序2,4,3顺序推送1的邻居的算法导致访问顺序1,3,2,4.无论1的邻居的推送顺序如何,2和3将在访问顺序中相邻,因为无论访问哪个首先将推动另一个尚未访问的,以及已访问过的另一个.


jea*_*mex 6

DFS逻辑应该是:

1)如果没有访问当前节点,请访问节点并将其标记为已访问
2)对于尚未访问的所有邻居,将它们推送到堆栈

例如,让我们用Java定义一个GraphNode类:

class GraphNode {
    int index;
    ArrayList<GraphNode> neighbors;
}
Run Code Online (Sandbox Code Playgroud)

这是没有递归的DFS:

void dfs(GraphNode node) {
    // sanity check
    if (node == null) {
        return;
    }

    // use a hash set to mark visited nodes
    Set<GraphNode> set = new HashSet<GraphNode>();

    // use a stack to help depth-first traversal
    Stack<GraphNode> stack = new Stack<GraphNode>();
    stack.push(node);

    while (!stack.isEmpty()) {
        GraphNode curr = stack.pop();

        // current node has not been visited yet
        if (!set.contains(curr)) {
            // visit the node
            // ...

            // mark it as visited
            set.add(curr);
        }

        for (int i = 0; i < curr.neighbors.size(); i++) {
            GraphNode neighbor = curr.neighbors.get(i);

            // this neighbor has not been visited yet
            if (!set.contains(neighbor)) {
                stack.push(neighbor);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用相同的逻辑递归地执行DFS,克隆图等.


arb*_*l84 5

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

And compare it with this:

// From recursive DFS
for (auto i&: adjacent) {
    if (!visited(i)) {
        dfs(i);
    }
}
Run Code Online (Sandbox Code Playgroud)

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.