简体   繁体   中英

Optimizing the algorithm of detecting cycle in a graph using python

I have implemented this code using dfs to detect if there is a cycle in a graph, and if there is, also print the vertices of this cycle. If there is more than one cycle, just print the first one that you've found. But somehow the OJ told me it's not efficient enough for some test cases. Any ideas about how to improve the efficiency of this piece of code?

I've been thinking hard about how to improve this, but no progress. I guess maybe I should try to use another algorithm other than dfs?

# using python3

from collections import defaultdict


class Graph():
    def __init__(self, V):
        self.V = V
        self.graph = defaultdict(list)

    def add_edge(self, u, v):
        self.graph[u].append(v)

    def dfs_walk(self, u):
        # List to contain the elements of a circle
        list_circle = list()
        # Mark visited vertexes
        visited = list()
        stack = [u]
        while stack:
            v = stack.pop()
            visited.append(v)
            # If already in list_circle, means there is a circle.
            if v in list_circle:
                return True, list_circle[list_circle.index(v):], visited
            # If v is not in list_circle and it has neighbor, collect it in the list,
            # go to next vertex. If it hasn't neighbor, check the left vertex
            else:
                # the next vertex is the first neighbor of this vertex
                if len(self.graph[v]) > 0:
                    stack.extend(self.graph[v])
                    list_circle.append(v)

        # Didn't find a circle in this round.
        return False, list_circle, visited

    def is_cyclic(self):
        control = [-1] * self.V
        for i in range(self.V):
            if control[i] == -1:
                flag, list_circle, visited = self.dfs_walk(i)
                for x in visited:
                    control[x] = 0
                if flag:
                    return True, list_circle
        # Didn't find any circle in all rounds.
        return False, list_circle


if __name__ == "__main__":
    line = input().split()
    V, E = int(line[0]), int(line[1])
    # Initialize the graph
    g = Graph(V)
    for r in range(E):
        row = input().split()
        start, end = int(row[0])-1, int(row[1])-1
        g.add_edge(start, end)

    flag, list_circle = g.is_cyclic()
    if flag:
        print("YES")
        print(" ".join(str(i+1) for i in list_circle))
    else:
        print("NO")

The first line is the number of vertices and number of edges. and after the first line, each line represents an edge (directed).

Input:

3 3

1 2

2 3

3 1

Output:

YES

1 2 3

I'd say this code is not very inefficient. Why do you have list_circle and visited as different things? Also storing them as list means that v in list_circle check takes O(n) so the whole algorithm is potentially O(n^2) . I think a bad example for you would be something like "P" but with a very small loop and with a very long line and you start at the bottom of that line so you have to travel the whole line until you finally find the cycle.

I suspect that if you merge them into a single dict() to store your DFS results as

visited[child] = parent

it will be harder to create a bad case for it and you still can easily re-construct a cycle from that information by travelling back along the two ways from the first double-visited point.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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