【问题标题】:Can I use just a simple Set for cycle detection in directed graph?我可以在有向图中仅使用一个简单的 Set 进行循环检测吗?
【发布时间】:2015-02-05 22:30:17
【问题描述】:

我见过相当多的算法来做循环检测,比如Tarjan's strongly connected components algorithmBest algorithm for detecting cycles in a directed graph中的回答。

我从没想过检测周期会那么复杂,我一直相信一个简单的Set 可以帮助解决问题。

因此,除了通常的marker 数组来记录访问过的顶点外,我们还可以使用额外的Set 来记录从源开始沿路径的所有顶点。

关键是记住在所有下一个邻居都完成后从集合中删除一个顶点。

一个简单的代码是这样的:

public boolean hasCycle(List<Integer>[] vs) {
  boolean[] marker = new boolean[v.length];
  Set<Integer> tracker = new HashSet<Integer>();

  for(int v = 0;v < vs.length;v++)
    if (explore(v, vs, marker, tracker)) return true;

  return false;
}

private boolean explore(int v, List<Integer>[] vs, boolean[] marker, Set<Integer> tracker) {
  if (tracker.contains(v)) return true;
  else if (!marker[v]) {
    marker[v] = true;
    tracker.add(v); // add current vertex to tracker
    for (int u:vs[v]) if (explore(v, vs, marker, tracker)) return true;
    tracker.remove(v); // remove the vertex from tracker, as it is fully done.
  }
  else return false;
}

这个算法有什么问题吗?


在 sasha 的回答的帮助下,实际上即使是集合也不是必需的,只需要一个数组就足够了。

public boolean hasCycle(List<Integer>[] vs) {
  boolean[] marker = new boolean[v.length];
  boolean[] onPath = new boolean[v.length];

  for(int v = 0;v < vs.length;v++)
    if (explore(v, vs, marker, onPath)) return true;

  return false;
}

private boolean explore(int v, List<Integer>[] vs, boolean[] marker, boolean[] onPath) {
  if (onPath[v]) return true;
  else if (!marker[v]) {
    marker[v] = true;
    onPath[v] = true; // add current vertex to the path
    for (int u:vs[v]) if (explore(v, vs, marker, onPath)) return true;
    onPath[v] = false; // remove the vertex from the path, as it is fully done.
  }
  else return false;
}

【问题讨论】:

  • 你是对的,简单的 DFS 足以检测图中的循环,就像你做的那样:检查后边。 Tarjan 的算法实际上计算了更复杂的东西
  • @NiklasB。但是这段代码或使用设置正确吗?
  • 每个循环都会在深度优先树中产生一个后沿,这就是您要检查的内容。对我来说看起来不错,但当然可能存在我忽略的细微实现错误。
  • 有些人更喜欢使用字节数组并使用 3 个不同的值来编码未访问/在根路径上/已访问的状态,而不是使用两个数组。这样会更节省空间。
  • @NiklasB。似乎是个好主意谢谢:)

标签: algorithm data-structures graph


【解决方案1】:

我不是 java 方面的专家,但谈论 C++ 以递归方式传递集合等并不省时。还 set 需要 O(log(n)) 插入/删除元素。你的逻辑在我看来是正确的。但是您可以通过保留两个数组 parent[]visited[] 来更高效、更轻松地完成此操作。基本上做一个 bfs 并且下面是伪代码( visited 被初始化为全零)。

   /* There are n nodes from 0 to n-1 */
   visited[0]=1
   parent[0]=0
   flag=0
   queue.push(0)
   while the queue is not empty
       top = queue.front()
       queue.pop()
       for all neighbors x of top
           if not visited[top]
              visited[x]=1
              parent[x]=top
              queue.push(x)
           else if visited[x] is 1 and parent[top] is not x
                flag = 1

   if flag is 1 cycle is there otherwise not

因为可能没有必要从 0 开始访问所有节点。所以重复直到所有节点都被访问。复杂度 O(E+V) 略好于您的方法的复杂度 O(E+VlogV)。但是写起来很简单,不递归。

【讨论】:

  • 您好,感谢您的回答。 Java 中的 HashSet 是 O(1) op,因为它不使用树。但是你是对的,设置不是必需的,因为一个简单的数组就足够了
  • 使用 DFS 优于 BFS 的优点是调用堆栈将包含循环的节点,而 BFS 则不是这样。
猜你喜欢
  • 2020-07-24
  • 2011-09-19
  • 2021-12-23
  • 1970-01-01
  • 2012-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多