给定一个无向图G=(V, E),其中有n个顶点(|V| = n),如何在O(n)的时间复杂度内确定它是否包含循环?
给定一个无向图G=(V, E),其中有n个顶点(|V| = n),如何在O(n)的时间复杂度内确定它是否包含循环?
我认为深度优先搜索可以解决这个问题。如果一条未被探索的边引导到一个之前访问过的节点,则图包含一个循环。该条件还使其具有O(n)时间复杂度,因为您最多可以探索n条边而不将其设置为true或留下没有未探索的边。
实际上,深度优先(或广度优先)搜索并不足够。您需要一个稍微复杂一些的算法。
例如,假设有一个节点为{a,b,c,d},边为{(a,b),(b,c),(b,d),(d,c)}的图形,其中边(x,y)是从x到y的边。 (看起来像这样,所有边都向下指向。)
(a)
|
|
(b)
/ \
(d) |
| |
\ /
(c)
然后进行深度优先搜索可能会访问节点(a),然后(b),然后(c),然后回溯到(b),然后访问(d),最后再次访问(c)并得出有一个循环——但实际上并没有。广度优先也会发生类似的情况。
你需要做的是记录你正在访问的节点。在上面的示例中,当算法到达(d)时,它已经完成了对(c)的访问,但还没有访问(a)或(b)。因此,重新访问一个已经完成的节点是可以的,但是访问一个未完成的节点意味着你有一个循环。通常的方法是将每个节点涂成白色(尚未访问)、灰色(正在访问后代)或黑色(完成访问)。
这里有一些伪代码!
define visit(node n):
if n.colour == grey: //if we're still visiting this node or its descendants
throw exception("Cycle found")
n.colour = grey //to indicate this node is being visited
for node child in n.children():
if child.colour == white: //if the child is unexplored
visit(child)
n.colour = black //to show we're done visiting this node
return
如果存在循环(最初所有节点应为白色),则运行visit(root_node)将抛出异常。
你可以使用深度优先搜索算法解决它。时间复杂度:O(n)
该算法的本质是,如果一个连通组件/图不包含环,它将始终是一棵树。请看这里的证明
让我们假设图没有环,即它是一棵树。 如果我们看一棵树,每个节点的边:
1.要么到达它唯一的父节点,即比它高一层。
2.或到达它的子节点,即比它低一层。
所以,如果一个节点有任何其他不在上述两种情况中的边,它显然会将该节点连接到除其父节点之外的其它祖先节点。这将形成一个环。
现在,事实已经清楚了,你只需要对图运行DFS(考虑到你的图是连通的,否则对所有未访问的顶点进行DFS),并且IF(如果)你找到一个节点的邻居被访问而且不是它的父节点,那么朋友们,图中就有一个环,你完成了。
你可以通过在为其邻居执行DFS时将父节点作为参数传递来跟踪父节点。 由于你最多只需要检查n条边,所以时间复杂度是O(n)。
希望这个答案有所帮助。
顺便说一下,如果您知道它是连接的,那么当且仅当|E|=|V|-1
时,它就是一棵树(因此没有循环)。当然,这不是一个小量的信息 :)
上述代码中使用的思想是:
如果发现/访问了一个已经发现/访问的节点,并且不是父节点,则表示存在一个环。
这也可以解释为(由@Rafał Dowgird提到)
如果未探索的边导致到达之前访问过的节点,则图包含一个环。
使用DFS算法进行遍历,条件是(parent != next node) 让我们看一下代码,然后理解其中的含义:
bool Graph::isCyclicUtil(int v, bool visited[], int parent)
{
// Mark the current node as visited
visited[v] = true;
// Recur for all the vertices adjacent to this vertex
list<int>::iterator i;
for (i = adj[v].begin(); i != adj[v].end(); ++i)
{
// If an adjacent is not visited, then recur for that adjacent
if (!visited[*i])
{
if (isCyclicUtil(*i, visited, v))
return true;
}
// If an adjacent is visited and not parent of current vertex,
// then there is a cycle.
else if (*i != parent)
return true;
}
return false;
}
如果DFS没有返回回溯边,则无向图是无环的(即森林)。
由于回溯边是连接顶点u
到深度优先树中祖先v
的边(u
, v
),因此没有回溯边意味着只有树边,所以没有环。
因此,我们可以简单地运行DFS。如果发现回溯边,则存在环。其复杂度为O(V)
而不是O(E + V)
。这是因为如果存在回溯边,则在看到|V|
个不同的边之前必须将其找到。这是因为在无环(无向)森林中,|E|≤|V|+1
。
我认为假设图是连通的可以很方便。因此,您可以使用上面展示的证明,运行时间为O(|V|)。如果不是,则|E|>|V|。 提醒:DFS的运行时间为O(|V|+|E|)。