如何使用递归 DFS 查找图是不是包含循环?

Posted

技术标签:

【中文标题】如何使用递归 DFS 查找图是不是包含循环?【英文标题】:How to find if a graph contains a cycle using a recursive DFS?如何使用递归 DFS 查找图是否包含循环? 【发布时间】:2017-10-19 21:38:39 【问题描述】:

下面的代码是深度优先的实现 搜索 DFS 以确定有向图是否有环。但是,它似乎有一个错误,因为它不起作用。我几乎 100% 确定该错误存在于if (visited[w]) 条件下。我的逻辑基本上是——如果一个节点已经被访问过,那么就存在一个循环。但是,if (visited[w]) 的问题在于,虽然条件可能为真,但并不一定意味着存在一个循环,因为该节点可能早就被访问过。

int *visited;  // array [0..V-1] of booleans

int checkCycle(Graph g)

   visited = malloc(sizeof(int)*g->numVertices);
   int result = dfsCycleCheck(g, 0);
   free(visited);
   return result;

int dfsCycleCheck(Graph g, Vertex v)

   visited[v] = 1;
   Vertex w;
   for (w = 0; w < nV(g); w++) 
      if (!hasEdge(g,v,w)) continue;
      if (visited[w]) return 1; // found cycle
      return dfsCycleCheck(g, w);
   
   return 0; // no cycle

【问题讨论】:

malloc() 不会初始化它提供给你的内存,你也不会自己初始化它。谁知道里面有什么?如果您希望它以全零开始,那么最简单的解决方案是使用 calloc() 而不是 malloc() 进行分配。 @JohnBollinger 我使用 for 循环将所有内容初始化为 0(上面未显示),但这显然不是问题 另外,您从节点 0 开始执行 DFS,但如果您的图表未连接,那么您可能会错过从该节点无法访问的循环。 目前尚不清楚您的图是否有有向边,但如果有,那么这也将考虑在内,这使得您更有可能因为您选择的起始节点而错过一个循环,并且更棘手来解决这个问题。 @JohnBollinger 该图始终是连接的(即所有节点都可以访问 - 没有孤立的节点)。至于有向/无向,我会说它是一个有向图,但无向的解决方案也很好 【参考方案1】:

您是正确的,没有办法判断被访问的节点是否已经被访问过,或者是否作为当前遍历的一部分被访问过。

一种方法是维护一个顶点数组,该数组可以保持三种状态,而不是我们已经拥有的两种状态。

WHITE :尚未处理顶点。最初 所有顶点都是白色的。

GRAY :正在处理顶点(DFS 用于此 顶点已开始,但尚未完成,这意味着 该顶点的所有后代(ind DFS 树) 尚未处理(或此顶点在功能 调用栈)

BLACK :顶点及其所有后代都是 已处理。

在进行 DFS 时,如果我们遇到从当前顶点到 灰色顶点,则这条边是后边,因此有一个循环。

代码将是这样的。

// Recursive function to find if there is back edge
// in DFS subtree tree rooted with 'u'
bool Graph::DFSUtil(int u, int color[])

    // GRAY :  This vertex is being processed (DFS
    //         for this vertex has started, but not
    //         ended (or this vertex is in function
    //         call stack)
    color[u] = GRAY;

    // Iterate through all adjacent vertices
    list<int>::iterator i;
    for (i = adj[u].begin(); i != adj[u].end(); ++i)
    
        int v = *i;  // An adjacent of u

        // If there is
        if (color[v] == GRAY)
          return true;

        // If v is not processed and there is a back
        // edge in subtree rooted with v
        if (color[v] == WHITE && DFSUtil(v, color))
          return true;
    

    // Mark this vertex as processed
    color[u] = BLACK;

    return false;


// Returns true if there is a cycle in graph
bool Graph::isCyclic()

    // Initialize color of all vertices as WHITE
    int *color = new int[V];
    for (int i = 0; i < V; i++)
        color[i] = WHITE;

    // Do a DFS traversal beginning with all
    // vertices
    for (int i = 0; i < V; i++)
        if (color[i] == WHITE)
           if (DFSUtil(i, color) == true)
              return true;

    return false;

这里的主要区别是节点可以被访问并且仍然是黑色的(表示该节点之前被访问过)或灰色(表示该节点作为当前遍历的一部分被访问;所以它是一个后边缘)帮助我们看看我们是否有一个周期。

这在之前是不可能的,因为我们没有区分两种类型的访问节点的布尔数组。

Source

【讨论】:

以上是关于如何使用递归 DFS 查找图是不是包含循环?的主要内容,如果未能解决你的问题,请参考以下文章

使用 DFS 查找两个节点之间的所有路径

在彩色图中查找具有单个不同颜色边的循环

循环有向图和无向图

使用递归 DFS 在二叉树中查找节点

递归函数中的for循环在递归结束后继续

两点之间简单路径的非递归 DFS 算法