给定 n 顶点的无向图 G =( V , E )(| V | = n ),如何在 O ( n )中找到它包含一个循环?
答案 0 :(得分:60)
我认为深度优先搜索解决了它。如果未探测的边缘导致之前访问过的节点,则该图形包含一个循环。这个条件也使它成为O(n),因为你可以探索最大的n个边而不将其设置为true或没有未探测的边缘。
答案 1 :(得分:30)
实际上,深度优先(或实际上是广度优先)搜索还不够。你需要一个更复杂的算法。
例如,假设存在节点{a,b,c,d}和边{(a,b),(b,c),(b,d),(d,c)}的图形,其中edge(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
然后运行访问(root_node)将抛出异常,当且仅当存在循环时(最初所有节点都应为白色)。
答案 2 :(得分:14)
没有周期的连通无向图G是树!任何树都有n - 1个边,因此我们可以简单地遍历图的边列表并计算边。如果我们计算n - 1个边,那么我们返回“是”,但如果我们到达第n个边,那么我们返回“否”。这需要O(n)时间,因为我们最多看n个边缘。
但如果图表没有连接,那么我们必须使用DFS。我们可以遍历边缘,如果任何未探测的边缘导致访问的顶点,则它具有循环。
答案 3 :(得分:12)
您可以使用DFS解决它。时间复杂度:O(n)
算法的本质是,如果连通的组件/图形不包含循环,它将永远是一个树。See here for proof
让我们假设图没有循环,即它是一棵树。 如果我们看一棵树,一个节点的每一条边:
1.要么达到它的唯一父母,就在它之上一层。
2.或者到达其下面一层的孩子。因此,如果一个节点有任何其他边缘不属于上述两个边缘,它显然会将节点连接到除其父节点之外的其祖先之一。这将形成一个循环。
既然事实清楚了,你所要做的就是为图形运行一个DFS(考虑到你的图是连接的,否则对所有未访问的顶点都是这样),如果你找到了一个VISITED节点的邻居而不是它的父母,那么我的朋友图中有一个CYCLE,你就完成了。
当您为其邻居执行DFS时,您可以通过简单地将父级作为参数传递来跟踪父级。 由于您只需要检查 n 边缘,因此时间复杂度为O(n)。
希望答案有所帮助。
答案 4 :(得分:7)
顺便说一句,如果你碰巧知道它是连接的,那么当且仅当|E|=|V|-1
时,它只是一棵树(因此没有循环)。当然,这不是一小部分信息:)
答案 5 :(得分:3)
答案是,真的,广泛的第一次搜索(或深度优先搜索,它并不重要)。细节在于分析。
现在,算法有多快?
首先,假设图表没有周期。然后边数是O(V),图是森林,达到目标。
现在,想象一下图表有周期,你的搜索算法将完成并报告第一个成功。图形是无向的,因此,当算法检查边缘时,只有两种可能性:要么它已经访问了边缘的另一端,要么它有,然后,这条边缘关闭一个圆圈。一旦它看到边缘的另一个顶点,那个顶点就会被“检查”,所以这些操作只有O(V)。在整个算法运行过程中,第二种情况只会达到一次。
答案 6 :(得分:2)
具有条件的DFS方式(父级!=下一个节点) 让我们看一下代码,然后了解发生了什么:
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;
}
上面的代码说明了自己,但我将尝试说明一种情况,即 * i!=父母 如果假设图是
1--2
然后,当我们在1处并转到2时,2的父项变为1,而当我们返回1时,因为1在2的相邻矩阵中,所以由于下一个顶点1也是2的父项,因此循环不会在此DFS方法中被检测为直接父对象。因此,代码工作正常
答案 7 :(得分:1)
以下是我用C编写的基于DFS的代码,用于确定给定的图是否连接/循环。最后有一些样本输出。希望它能提供帮助:)
#include<stdio.h>
#include<stdlib.h>
/****Global Variables****/
int A[20][20],visited[20],v=0,count=0,n;
int seq[20],s=0,connected=1,acyclic=1;
/****DFS Function Declaration****/
void DFS();
/****DFSearch Function Declaration****/
void DFSearch(int cur);
/****Main Function****/
int main()
{
int i,j;
printf("\nEnter no of Vertices: ");
scanf("%d",&n);
printf("\nEnter the Adjacency Matrix(1/0):\n");
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&A[i][j]);
printf("\nThe Depth First Search Traversal:\n");
DFS();
for(i=1;i<=n;i++)
printf("%c,%d\t",'a'+seq[i]-1,i);
if(connected && acyclic) printf("\n\nIt is a Connected, Acyclic Graph!");
if(!connected && acyclic) printf("\n\nIt is a Not-Connected, Acyclic Graph!");
if(connected && !acyclic) printf("\n\nGraph is a Connected, Cyclic Graph!");
if(!connected && !acyclic) printf("\n\nIt is a Not-Connected, Cyclic Graph!");
printf("\n\n");
return 0;
}
/****DFS Function Definition****/
void DFS()
{
int i;
for(i=1;i<=n;i++)
if(!visited[i])
{
if(i>1) connected=0;
DFSearch(i);
}
}
/****DFSearch Function Definition****/
void DFSearch(int cur)
{
int i,j;
visited[cur]=++count;
seq[count]=cur;
for(i=1;i<count-1;i++)
if(A[cur][seq[i]])
acyclic=0;
for(i=1;i<=n;i++)
if(A[cur][i] && !visited[i])
DFSearch(i);
}
/ *示例输出:
majid@majid-K53SC:~/Desktop$ gcc BFS.c
majid@majid-K53SC:~/Desktop$ ./a.out
************************************
Enter no of Vertices: 10
Enter the Adjacency Matrix(1/0):
0 0 1 1 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 1 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 1 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0 0 0
The Depdth First Search Traversal:
a,1 c,2 d,3 f,4 b,5 e,6 g,7 h,8 i,9 j,10
It is a Not-Connected, Cyclic Graph!
majid@majid-K53SC:~/Desktop$ ./a.out
************************************
Enter no of Vertices: 4
Enter the Adjacency Matrix(1/0):
0 0 1 1
0 0 1 0
1 1 0 0
0 0 0 1
The Depth First Search Traversal:
a,1 c,2 b,3 d,4
It is a Connected, Acyclic Graph!
majid@majid-K53SC:~/Desktop$ ./a.out
************************************
Enter no of Vertices: 5
Enter the Adjacency Matrix(1/0):
0 0 0 1 0
0 0 0 1 0
0 0 0 0 1
1 1 0 0 0
0 0 1 0 0
The Depth First Search Traversal:
a,1 d,2 b,3 c,4 e,5
It is a Not-Connected, Acyclic Graph!
*/
答案 8 :(得分:1)
一个简单的DFS可以检查给定的无向图是否有周期。
Here's the C++ code to the same.
上述代码中使用的提示是:
如果再次找到已发现/访问过的节点,则为 不是父节点,那么我们有一个循环。
这也可以解释如下(由@RafałDowgird提及
如果未探测的边缘导致之前访问过的节点,则该图形包含一个循环。
答案 9 :(得分:1)
如果DFS不产生后沿,则无向图是非循环的(即森林)。
因为后边缘是将顶点u
连接到祖先的那些边(v
,u
)
在深度优先的树中v
,所以没有后边缘意味着只有树边缘,所以
没有循环。
所以我们可以简单地运行DFS。如果找到后缘,则有一个循环。复杂性
是O(V)
而不是O(E + V)
。因为如果有后缘,它必须
在看到|V|
个明显边缘之前找到。这是因为在非环状(无向)森林中,|E| ≤ |V| + 1
。
答案 10 :(得分:1)
我认为图表连接的假设很少。因此,您可以使用上面显示的证明,即运行时间为O(| V |)。如果没有,那么| E |&gt; | V |。 提醒:DFS的运行时间为 O(| V | + | E |)。
答案 11 :(得分:1)
我最近开始研究图表。我在java中编写了一段代码,可以确定图形是否有循环。我使用DFT在图中查找周期。我使用堆栈来遍历图表而不是递归。
在高级别使用堆栈的DFT在以下步骤中完成
我从Graph的每个节点执行了DFT,并且在遍历期间如果我遇到了我之前访问过的顶点,我检查了顶点的堆栈深度是否大于1。 我还检查了一个节点是否有自己的边缘以及节点之间是否有多个边缘。 我最初写的堆栈版本不是很优雅。我读了伪代码,它是如何使用递归完成的,它很整洁。这是一个java实现。 LinkedList数组表示图形。每个节点及其相邻的顶点分别由数组的索引和每个项目表示
class GFG {
Boolean isCyclic(int V, LinkedList<Integer>[] alist) {
List<Integer> visited = new ArrayList<Integer>();
for (int i = 0; i < V; i++) {
if (!visited.contains(i)) {
if (isCyclic(i, alist, visited, -1))
return true;
}
}
return false;
}
Boolean isCyclic(int vertex, LinkedList<Integer>[] alist, List<Integer> visited, int parent) {
visited.add(vertex);
for (Iterator<Integer> iterator = alist[vertex].iterator(); iterator.hasNext();) {
int element = iterator.next();
if (!visited.contains(element)) {
if (isCyclic(element, alist, visited, vertex))
return true;
} else if (element != parent)
return true;
}
return false;
}
}
答案 12 :(得分:1)
您可以使用boost graph library和cyclic dependencies。它具有查找具有back_edge
函数的循环的解决方案。
答案 13 :(得分:0)
正如其他人所说...... 深度优先搜索将解决它。 通常,深度优先搜索采用O(V + E),但在这种情况下,您知道图形最多具有O(V)边缘。所以你可以简单地运行一个DFS,一旦你看到一个新的边缘增加一个计数器。当计数器达到V时,您不必继续,因为图表肯定有一个周期。 显然这需要O(v)。
答案 14 :(得分:0)
我认为正确使用DFS还取决于您将如何在代码中表示图形。例如,假设您使用相邻列表来跟踪邻居节点,并且您的图形有2个顶点且只有一个边:V = {1,2}和E = {(1,2)}。在这种情况下,从顶点1开始,DFS会将其标记为VISITED并将2放入队列中。之后它会弹出顶点2,因为1与2相邻,1是VISITED,DFS会断定有一个循环(这是错误的)。 换句话说,无向图(1,2)和(2,1)是相同的边,你应该以DFS的方式编写代码,不要将它们视为不同的边。为每个访问节点保留父节点将有助于处理这种情况。
答案 15 :(得分:0)
这是C ++算法的一种简单实现,用于检查图形是否在O(n)
时间内具有循环(n是图形中的顶点数)。我这里没有显示Graph数据结构的实现(为了简短起见)。该算法期望Graph类具有公共方法vector<int> getAdj(int v)
,该方法返回与v
相邻的顶点,而int getV()
返回顶点总数。此外,该算法还假定图的顶点从0 to n - 1
开始编号。
class CheckCycleUndirectedGraph
{
private:
bool cyclic;
vector<bool> visited;
void depthFirstSearch(const Graph& g, int v, int u) {
visited[v] = true;
for (auto w : g.getAdj(v)) {
if (!visited[w]) {
depthFirstSearch(g, w, v);
}
else if (w != u) {
cyclic = true;
return;
}
}
}
public:
CheckCycleUndirectedGraph(const Graph& g) : cyclic(false) {
visited = vector<bool>(g.getV(), false);
for (int v = 0; v < g.getV(); v++) {
if (!visited[v]){
depthFirstSearch(g, v, v);
if(cyclic)
break;
}
}
}
bool containsCycle() const {
return cyclic;
}
};
请记住,Graph可能包含几个未连接的组件,并且这些组件内部可能存在循环。所示算法还可以检测此类图中的循环。
答案 16 :(得分:-2)
无循环的无向图有| E | &LT; | V | -1
public boolean hasCycle(Graph g) {
int totalEdges = 0;
for(Vertex v : g.getVertices()) {
totalEdges += v.getNeighbors().size();
}
return totalEdges/2 > g.getVertices().size - 1;
}