简体   繁体   English

无向图中的循环

[英]Cycles in an Undirected Graph

给定一个有n个顶点 (| V | = n ) 的无向图G =( V , E ),你如何确定它是否包含O ( n ) 中的环?

I think that depth first search solves it.我认为深度优先搜索可以解决这个问题。 If an unexplored edge leads to a node visited before, then the graph contains a cycle.如果一条未探索的边导致之前访问过的节点,则该图包含一个循环。 This condition also makes it O(n), since you can explore maximum n edges without setting it to true or being left with no unexplored edges.此条件也使其成为 O(n),因为您可以探索最大 n 条边而无需将其设置为 true 或留下任何未探索的边。

Actually, depth first (or indeed breadth first) search isn't quite enough.实际上,深度优先(或实际上广度优先)搜索还不够。 You need a sightly more complex algorithm.你需要一个看起来更复杂的算法。

For instance, suppose there is graph with nodes {a,b,c,d} and edges {(a,b),(b,c),(b,d),(d,c)} where an edge (x,y) is an edge from x to y.例如,假设有一个节点 {a,b,c,d} 和边 {(a,b),(b,c),(b,d),(d,c)} 的图,其中边 (x ,y) 是从 x 到 y 的边。 (looks something like this, with all edges directed downwards.) (看起来像这样,所有边缘都朝下。)

    (a)
     |
     |
    (b)
    / \ 
  (d)  |
   |   |
    \ /
    (c)

Then doing depth first search may visit node (a), then (b), then (c), then backtrack to (b), then visit (d), and finally visit (c) again and conclude there is a cycle -- when there isn't.然后进行深度优先搜索可能访问节点(a),然后(b),然后(c),然后回溯到(b),然后访问(d),最后再次访问(c)并得出结论有一个循环 -当没有。 A similar thing happens with breadth first.广度优先也会发生类似的事情。

What you need to do is keep track of which nodes your in the middle of visiting.您需要做的是跟踪您在访问过程中的哪些节点。 In the example above, when the algorithm reaches (d) it has finished visiting (c) but not (a) or (b).在上面的示例中,当算法到达 (d) 时,它已完成访问 (c) 但未完成 (a) 或 (b)。 So revisiting a finished node is fine, but visiting an unfinished node means you have a cycle.所以重新访问一个完成的节点是好的,但访问一个未完成的节点意味着你有一个循环。 The usual way to do this is colour each node white(not yet visited), grey(visiting descendants) or black(finished visiting).通常的做法是将每个节点涂成白色(尚未访问)、灰色(访问后代)或黑色(已完成访问)。

here is some pseudo code!这是一些伪代码!

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

then running visit(root_node) will throw an exception if and only if there is a cycle (initially all nodes should be white).然后运行 ​​visit(root_node) 当且仅当存在循环时才会抛出异常(最初所有节点都应该是白色的)。

A connected, undirected graph G that has no cycles is a tree!没有环的连通无向图 G 是一棵树! Any tree has exactly n − 1 edges, so we can simply traverse the edge list of the graph and count the edges.任何树都恰好有 n-1 条边,因此我们可以简单地遍历图的边列表并计算边数。 If we count n − 1 edges then we return “yes” but if we reach the nth edge then we return “no”.如果我们计算 n − 1 条边,则返回“是”,但如果我们到达第 n 条边,则返回“否”。 This takes O (n) time because we look at at most n edges.这需要 O (n) 时间,因为我们最多查看 n 条边。

But if the graph is not connected,then we would have to use DFS.但是如果图没有连通,那么我们就必须使用 DFS。 We can traverse through the edges and if any unexplored edges lead to the visited vertex then it has cycle.我们可以遍历边,如果任何未探索的边通向访问过的顶点,那么它就有循环。

You can solve it using DFS.您可以使用 DFS 解决它。 Time complexity: O(n)时间复杂度:O(n)

The essence of the algorithm is that if a connected component/graph does NOT contain a CYCLE, it will always be a TREE.该算法的本质是,如果连接组件/图不包含 CYCLE,则它将始终是 TREE。 See here for proof看这里证明

Let us assume the graph has no cycle, ie it is a tree.让我们假设图没有环,即它是一棵树。 And if we look at a tree, each edge from a node:如果我们看一棵树,来自一个节点的每条边:

1.either reaches to its one and only parent, which is one level above it. 1.either 到达其唯一的父级,它比它高一级。

2.or reaches to its children, which are one level below it. 2. 或到达它的子级,它比它低一级。

So if a node has any other edge which is not among the two described above, it will obviously connect the node to one of its ancestors other than its parent.因此,如果一个节点有任何其他不属于上述两条边的边,它显然会将该节点连接到其父节点以外的一个祖先节点。 This will form a CYCLE.这将形成一个循环。

Now that the facts are clear, all you have to do is run a DFS for the graph (considering your graph is connected, otherwise do it for all unvisited vertices), and IF you find a neighbor of the node which is VISITED and NOT its parent, then my friend there is a CYCLE in the graph, and you're DONE.现在事实已经清楚了,您所要做的就是为图运行 DFS(考虑到您的图是连接的,否则对所有未访问的顶点执行此操作),并且如果您找到已访问的节点的邻居而不是它的父母,那么我的朋友,图中有一个 CYCLE,您就完成了。

You can keep track of parent by simply passing the parent as parameter when you do DFS for its neighbors.当您对其邻居执行 DFS 时,您可以通过简单地将父项作为参数传递来跟踪父项。 And Since you only need to examine n edges at the most, the time complexity will be O(n).并且由于您最多只需要检查n 条边,因此时间复杂度将为 O(n)。

Hope the answer helped.希望答案有所帮助。

By the way, if you happen to know that it is connected, then simply it is a tree (thus no cycles) if and only if |E|=|V|-1 .顺便说一下,如果你碰巧知道它是连通的,那么它就是一棵树(因此没有循环)当且仅当|E|=|V|-1 Of course that's not a small amount of information :)当然,这不是少量的信息:)

The answer is, really, breadth first search (or depth first search, it doesn't really matter).答案是,真的,广度优先搜索(或深度优先搜索,这并不重要)。 The details lie in the analysis.细节在于分析。

Now, how fast is the algorithm?现在,算法有多快?

First, imagine the graph has no cycles.首先,假设图形没有环。 The number of edges is then O(V), the graph is a forest, goal reached.边的数量是 O(V),图是一个森林,目标达到。

Now, imagine the graph has cycles, and your searching algorithm will finish and report success in the first of them.现在,假设图形有循环,您的搜索算法将在第一个循环中完成并报告成功。 The graph is undirected, and therefore, the when the algorithm inspects an edge, there are only two possibilities: Either it has visited the other end of the edge, or it has and then, this edge closes a circle.该图是无向图,因此,当算法检查一条边时,只有两种可能性:要么它已经访问了边的另一端,要么它已经访问了这条边,然后这条边闭合了一个圆。 And once it sees the other vertex of the edge, that vertex is "inspected", so there are only O(V) of these operations.一旦它看到边缘的另一个顶点,该顶点就会被“检查”,因此这些操作只有 O(V) 次。 The second case will be reached only once throughout the run of the algorithm.在整个算法运行过程中,第二种情况只会出现一次。

DFS APPROACH WITH A CONDITION(parent != next node) Let's see the code and then understand what's going on :带条件的 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; 
} 

The above code explains itself but I will try to explain one condition ie *i != parent Here if suppose graph is上面的代码解释了自己,但我会尝试解释一个条件,即 *i != parent 如果假设图是

1--2 1--2

Then when we are at 1 and goes to 2, the parent for 2 becomes 1 and when we go back to 1 as 1 is in adj matrix of 2 then since next vertex 1 is also the parent of 2 Therefore cycle will not be detected for the immediate parent in this DFS approach.然后,当我们在 1 并转到 2 时,2 的父节点变为 1,当我们返回 1 时,因为 1 在 2 的 adj 矩阵中,那么由于下一个顶点 1 也是 2 的父节点,因此不会检测到循环此 DFS 方法中的直接父级。 Hence Code works fine因此代码工作正常

I believe that the assumption that the graph is connected can be handful.我相信图是连通的假设可能是少数。 thus, you can use the proof shown above, that the running time is O(|V|).因此,您可以使用上面显示的证明,即运行时间为 O(|V|)。 if not, then |E|>|V|.如果不是,则 |E|>|V|。 reminder: the running time of DFS is O(|V|+|E|) .提醒:DFS 的运行时间是O(|V|+|E|)

Here is the code I've written in C based on DFS to find out whether a given graph is connected/cyclic or not.这是我基于 DFS 用 C 编写的代码,用于确定给定的图是否连接/循环。 with some sample output at the end.最后有一些示例输出。 Hope it'll be helpful :)希望它会有所帮助:)

#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);

    }

/*Sample Output: /*样本输出:

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!

*/

A simple DFS does the work of checking if the given undirected graph has a cycle or not.一个简单的 DFS 会检查给定的无向图是否有环。

Here's the C++ code to the same. 这是相同的 C++ 代码。

The idea used in the above code is:上面代码中使用想法是:

If a node which is already discovered/visited is found again and is not the parent node , then we have a cycle.如果一个已经被发现/访问过的节点再次被发现并且不是父节点,那么我们就有了一个循环。

This can also be explained as below(mentioned by @Rafał Dowgird这也可以解释如下(@Rafał Dowgird 提到

If an unexplored edge leads to a node visited before, then the graph contains a cycle.如果一条未探索的边导致之前访问过的节点,则该图包含一个循环。

An undirected graph is acyclic (ie, a forest) if a DFS yields no back edges.如果 DFS 不产生后边,则无向图是无环的(即森林)。 Since back edges are those edges ( u , v ) connecting a vertex u to an ancestor v in a depth-first tree, so no back edges means there are only tree edges, so there is no cycle.由于后边是在深度优先树中将顶点u连接到祖先v那些边 ( u , v ),所以没有后边意味着只有树边,所以没有循环。 So we can simply run DFS.所以我们可以简单地运行 DFS。 If find a back edge, there is a cycle.如果找到后缘,则存在循环。 The complexity is O(V) instead of O(E + V) .复杂度是O(V)而不是O(E + V) Since if there is a back edge, it must be found before seeing |V|因为如果有后边,必须在看到|V|之前找到它|V| distinct edges.明显的边缘。 This is because in a acyclic (undirected) forest, |E| ≤ |V| + 1这是因为在无环(无向)森林中, |E| ≤ |V| + 1 |E| ≤ |V| + 1 |E| ≤ |V| + 1 . |E| ≤ |V| + 1

As others have mentioned... A depth first search will solve it.正如其他人所提到的......深度优先搜索将解决它。 In general depth first search takes O(V + E) but in this case you know the graph has at most O(V) edges.一般来说,深度优先搜索需要 O(V + E),但在这种情况下,您知道该图最多有 O(V) 条边。 So you can simply run a DFS and once you see a new edge increase a counter.所以你可以简单地运行一个 DFS,一旦你看到一个新的边缘,就会增加一个计数器。 When the counter has reached V you don't have to continue because the graph has certainly a cycle.当计数器达到 V 时,您不必继续,因为该图肯定有一个循环。 Obviously this takes O(v).显然这需要 O(v)。

I believe using DFS correctly also depends on how are you going to represent your graph in the code.我相信正确使用 DFS 还取决于您将如何在代码中表示您的图形。 For example suppose you are using adjacent lists to keep track of neighbor nodes and your graph has 2 vertices and only one edge: V={1,2} and E={(1,2)}.例如,假设您使用相邻列表来跟踪邻居节点,并且您的图形有 2 个顶点和只有一条边:V={1,2} 和 E={(1,2)}。 In this case starting from vertex 1, DFS will mark it as VISITED and will put 2 in the queue.在这种情况下,从顶点 1 开始,DFS 会将其标记为 VISITED 并将 2 放入队列中。 After that it will pop vertex 2 and since 1 is adjacent to 2, and 1 is VISITED, DFS will conclude that there is a cycle (which is wrong).之后它将弹出顶点 2,并且由于 1 与 2 相邻,并且 1 被访问,DFS 将得出结论,存在一个循环(这是错误的)。 In other words in Undirected graphs (1,2) and (2,1) are the same edge and you should code in a way for DFS not to consider them different edges.换句话说,在无向图中 (1,2) 和 (2,1) 是相同的边,您应该以一种方式进行编码,让 DFS 不要将它们视为不同的边。 Keeping parent node for each visited node will help to handle this situation.为每个访问过的节点保留父节点将有助于处理这种情况。

I started studying graphs recently.我最近开始学习图表。 I wrote a piece of code in java that could determine if a graph has cycles.我用java写了一段代码,可以确定一个图是否有循环。 I used DFT to find cycles in the graph.我使用 DFT 在图中找到循环。 Instead of recurssion I used a stack to traverse the graph.我没有使用递归,而是使用堆栈来遍历图形。

At a high level DFT using a stack is done in the following steps使用堆栈的高级 DFT 在以下步骤中完成

  1. Visit a Node访问节点
  2. If the node is not in the visited list add it to the list and push it to the top of the stack如果该节点不在访问列表中,则将其添加到列表中并将其压入栈顶
  3. Mark the node at the top of the stack as the current node.将栈顶的节点标记为当前节点。
  4. Repeat the above for each adjacent node of the current node对当前节点的每个相邻节点重复以上操作
  5. If all the nodes have been visited pop the current node off the stack如果所有节点都被访问过,则将当前节点从堆栈中弹出

I performed a DFT from each node of the Graph and during the traversal if I encountered a vertex that I visited earlier, I checked if the vertex had a stack depth greater than one.我对图的每个节点执行了 DFT,在遍历过程中,如果遇到我之前访问过的顶点,我会检查该顶点的堆栈深度是否大于 1。 I also checked if a node had an edge to itself and if there were multiple edges between nodes.我还检查了一个节点是否有自己的边,以及节点之间是否有多个边。 The stack version that I originally wrote was not very elegant.我最初写的堆栈版本不是很优雅。 I read the pseudo code of how it could be done using recursion and it was neat.我阅读了如何使用递归完成的伪代码,它很简洁。 Here is a java implementation.这是一个java实现。 The LinkedList array represents a graph. LinkedList 数组表示一个图形。 with each node and it's adjacent vertices denoted by the index of the array and each item respectively每个节点及其相邻顶点分别由数组和每个项目的索引表示

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;
}

} }

Here is a simple implementation in C++ of algorithm that checks if a graph has cycle(s) in O(n) time (n is number of vertexes in the Graph).这是算法的 C++ 中的一个简单实现,用于检查图是否在O(n)时间内具有循环(n 是图中的顶点数)。 I do not show here the Graph data structure implementation (to keep answer short).我没有在这里展示 Graph 数据结构的实现(为了保持简短的回答)。 The algorithms expects the class Graph to have public methods, vector<int> getAdj(int v) that returns vertexes adjacent to the v and int getV() that returns total number of vertexes.算法期望 Graph 类具有公共方法, vector<int> getAdj(int v)返回与v相邻的顶点, int getV()返回顶点总数。 Additionally, the algorithms assumes the vertexes of the Graph are numbered from 0 to n - 1 .此外,这些算法假设图的顶点编号为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;
    }
};

Keep in mind that Graph may consist of several not connected components and there may be cycles inside of the components.请记住,Graph 可能由多个未连接的组件组成,并且组件内部可能存在循环。 The shown algorithms detects cycles in such graphs as well.所示算法也检测此类图中的循环。

You can use boost graph library and cyclic dependencies .您可以使用boost 图库循环依赖 It has the solution for finding cycles with back_edge function.它具有使用back_edge函数查找循环的解决方案。

An undirected graph without cycle has |E|无环无向图有|E| < |V|-1. < |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;

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM