【问题标题】:Iterative DFS vs Recursive DFS and different elements order迭代 DFS 与递归 DFS 和不同的元素顺序
【发布时间】:2012-02-08 20:48:34
【问题描述】:

我写了一个递归DFS算法来遍历一个图:

void Graph<E, N>::DFS(Node n)
{
    std::cout << ReadNode(n) << " ";

    MarkVisited(n);

    NodeList adjnodes = Adjacent(n);

    NodeList::position pos = adjnodes.FirstPosition();

    while(!adjnodes.End(pos))
    {
        Node adj = adjnodes.ReadList(pos);

        if(!IsMarked(adj))
            DFS(adj);

        pos = adjnodes.NextPosition(pos);
    }
}

然后我写了一个使用堆栈的迭代 DFS 算法:

template <typename E, typename N>
void Graph<E, N>::IterativeDFS(Node n)
{
    Stack<Node> stack;

    stack.Push(n);

    while(!stack.IsEmpty())
    {
        Node u = stack.Read();

        stack.Pop();

        if(!IsMarked(u))
        {
            std::cout << ReadNode(u) << " ";

            MarkVisited(u);

            NodeList adjnodes = Adjacent(u);

            NodeList::position pos = adjnodes.FirstPosition();

            while(!adjnodes.End(pos))
            {
                stack.Push(adjnodes.ReadList(pos));

                pos = adjnodes.NextPosition(pos);
            }
        }
    }

我的问题是,在一个图中,例如,我输入三个节点 'a'、'b'、'c' 与弧 ('a', 'b') 和 ('a', ' c') 我的输出是:

'a'、'b'、'c' 与递归 DFS 版本,以及:

'a', 'c', 'b' 与迭代 DFS 之一。

我怎样才能得到相同的订单?我是不是做错了什么?

谢谢!

【问题讨论】:

  • 两个订单都是有效的 DFS 订单,因为节点 'b' 和 'c' 都可以从节点 'a' 一跳到达。您是否担心这会产生无效的订购?还是您只是希望这两种算法(似乎都产生有效的排序)产生相同的排序?
  • 我知道使用堆栈我应该能够以迭代的方式模拟递归函数,那为什么我得不到相同的输出呢?
  • 在迭代解决方案中,当您将顶点的邻接列表压入堆栈时,在将它们压入堆栈之前将列表反转。

标签: c++ algorithm graph depth-first-search traversal


【解决方案1】:

两者都是有效的 DFS 算法。 DFS 不指定您首先看到的节点。这并不重要,因为边之间的顺序没有定义[记住:边通常是一个集合]。不同之处在于您处理每个节点的子节点的方式。

迭代方法中:您首先将所有元素插入堆栈 - 然后处理堆栈的头部 [这是插入的最后一个节点] - 因此 第一个节点句柄是最后一个孩子

递归方法中:您在看到每个节点时处理它。因此,您处理的第一个节点是第一个子节点

要使迭代 DFS 产生与递归 DFS 相同的结果 - 您需要以相反的顺序将元素添加到堆栈 [对于每个节点,首先插入其最后一个子节点,最后插入其第一个子节点]

【讨论】:

  • 哦,我明白了。所以这是关于以正确的顺序推送堆栈中的元素。我试图从最后一个元素读取列表到第一个元素,现在它可以工作了。谢谢!
  • @JohnQ:不客气。很高兴它对您有所帮助,我希望您不仅了解如何解决这些问题,还了解它发生的原因以及将来如何识别它。
  • 是的,当然。我已经画了一些方案来了解情况:)
  • OP 的代码最终不是广度优先搜索吗?深度优先应该首先沿着第一条路径走下去,不是吗?
  • @Claudiu 不,代码确实先深入,探索节点顺序的唯一变化是访问兄弟节点的顺序。这与 DFS 的正确性并不矛盾,因为节点和边通常被认为是 集合,根据定义,它是无序的
【解决方案2】:

最明显的差异是您使用子级的顺序。

在递归方法中:你带第一个孩子并在它出现时立即运行

在迭代方法中:您将所有子代入栈,然后取栈顶,即最后一个子代

要产生相同的结果,只需以相反的顺序插入子项。

另一个差异是内存使用情况,因为一个会使用调用堆栈,而另一个会使用您创建的堆栈或 STL 元素之一:

你可以在这里阅读:https://codeforces.com/blog/entry/17307

【讨论】:

    【解决方案3】:

    在这里,我递归地留下我的解决方案,实施起来非常快。只需针对需要使用此算法的任何问题进行调整即可。

    将当前状态标记为已访问非常重要,定义为ok[u] = true,即使所有未访问过的状态都使用memset(ok, 0, sizeof ok)

    #define forn(i , a , b) for(int i=(a);i<(b);i++)
    
    vector<int> arr[10001];
    bool ok[10001];
    
    void addE(int u , int v){
      arr[u].push_back(v);
      arr[v].push_back(u);
    }
    
    void dfs(int u){
      ok[u] = true;
      forn(v , 0 , (int)arr[u].size()) if(!ok[arr[u][v]]) dfs(arr[u][v]);
    }
    
    int main(){
      //...
      memset(ok , 0 , sizeof ok);
      //... 
      return 0;
    }
    

    【讨论】:

      【解决方案4】:

      下面是 C# 中邻接矩阵的示例代码(根据上面的 @amit 答案)。

      using System;
      using System.Collections.Generic;
      
      namespace GraphAdjMatrixDemo
      {
          public class Program
          {
              public static void Main(string[] args)
              {
                  // 0  1  2  3  4  5  6
                  int[,] matrix = {     {0, 1, 1, 0, 0, 0, 0},
                                        {1, 0, 0, 1, 1, 1, 0},
                                        {1, 0, 0, 0, 0, 0, 1},
                                        {0, 1, 0, 0, 0, 0, 1},
                                        {0, 1, 0, 0, 0, 0, 1},
                                        {0, 1, 0, 0, 0, 0 ,0},
                                        {0, 0, 1, 1, 1, 0, 0}  };
      
                  bool[] visitMatrix = new bool[matrix.GetLength(0)];
                  Program ghDemo = new Program();
      
                  for (int lpRCnt = 0; lpRCnt < matrix.GetLength(0); lpRCnt++)
                  {
                      for (int lpCCnt = 0; lpCCnt < matrix.GetLength(1); lpCCnt++)
                      {
                          Console.Write(string.Format(" {0}  ", matrix[lpRCnt, lpCCnt]));
                      }
                      Console.WriteLine();
                  }
      
                  Console.Write("\nDFS Recursive : ");
                  ghDemo.DftRecursive(matrix, visitMatrix, 0);
                  Console.Write("\nDFS Iterative : ");
                  ghDemo.DftIterative(matrix, 0);
      
                  Console.Read();
              }
      
              //====================================================================================================================================
      
              public void DftRecursive(int[,] srcMatrix, bool[] visitMatrix, int vertex)
              {
                  visitMatrix[vertex] = true;
                  Console.Write(vertex + "  ");
      
                  for (int neighbour = 0; neighbour < srcMatrix.GetLength(0); neighbour++)
                  {
                      if (visitMatrix[neighbour] == false && srcMatrix[vertex, neighbour] == 1)
                      {
                          DftRecursive(srcMatrix, visitMatrix, neighbour);
                      }
                  }
              }
      
              public void DftIterative(int[,] srcMatrix, int srcVertex)
              {
                  bool[] visited = new bool[srcMatrix.GetLength(0)];
      
                  Stack<int> vertexStack = new Stack<int>();
                  vertexStack.Push(srcVertex);
      
                  while (vertexStack.Count > 0)
                  {
                      int vertex = vertexStack.Pop();
      
                      if (visited[vertex])
                          continue;
      
                      Console.Write(vertex + "  ");
                      visited[vertex] = true;
      
                      for (int neighbour = srcMatrix.GetLength(0) - 1; neighbour >= 0; neighbour--)
                      //for (int neighbour = 0; neighbour < srcMatrix.GetLength(0); neighbour++)
                      {
                          if (srcMatrix[vertex, neighbour] == 1 && visited[neighbour] == false)
                          {
                              vertexStack.Push(neighbour);
                          }
                      }
                  }
              }
          }
      }
      

      【讨论】:

      • 你的代码做错了。请注意,我的答案指定您只需要以相反的顺序 PUSH 元素,而实际上您在代码中还做了两件事:(1)更改检查“已访问”的位置。 (2) 更改打印元素的位置(在您的代码中:当它被插入到列表中时,而不是从列表中弹出时)。我拿了你的代码并修复了上述两个流程,它在ideone 中可用,并且工作正常。希望我没有错过任何其他问题。
      • 详述:关于(1):递归调用在探索节点时设置visited,在插入节点时迭代代码。关于(2):递归调用中的打印是在探索节点时(从堆栈中弹出),并且在迭代时将其推入堆栈。如果您尝试两种递归解决方案,而上述注意事项的每个变体都有一个,那么这两个差异将导致不同的结果。
      • 感谢您的快速回复和纠正我@amit。我几乎和你提到的一样做了(错过了最新的),我在代码中缺少的逻辑是 if (visited[vertex] == true) continue;我对你的答案和评论投了赞成票:) 并为其他人更新了我的矩阵示例答案。
      猜你喜欢
      • 2015-01-17
      • 1970-01-01
      • 1970-01-01
      • 2015-02-27
      • 1970-01-01
      • 2020-03-01
      • 1970-01-01
      • 2018-10-25
      • 1970-01-01
      相关资源
      最近更新 更多