使用python优化图形中检测周期的算法

时间:2019-01-25 00:50:47

标签: python algorithm graph

我已经使用dfs实现了此代码,以检测图中是否有一个循环,如果有,还打印该循环的顶点。如果有多个循环,则只需打印找到的第一个循环即可。但是OJ以某种方式告诉我,对于某些测试用例而言,效率还不够高。关于如何提高这段代码效率的任何想法吗?

我一直在认真思考如何改善这一点,但没有任何进展。我想也许我应该尝试使用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")

第一行是顶点数和边数。在第一行之后,每行代表一条边(有向)。

输入:

3 3

1 2

2 3

3 1

输出:

1 2 3

1 个答案:

答案 0 :(得分:1)

我会说这段代码效率不是很高。为什么您将list_circlevisited当作不同的东西?同样将它们存储为list意味着v in list_circle校验需要O(n),因此整个算法可能是O(n^2)。我认为对您来说,一个不好的例子是“ P”,但循环很小,行很长,您从该行的底部开始,因此您必须遍历整个行,直到最终找到循环为止。

我怀疑如果将它们合并到一个dict()中以将DFS结果存储为

visited[child] = parent

为其创建一个糟糕的案例将变得更加困难,并且您仍然可以通过从第一个双重访问的点沿两种方式返回来轻松地根据该信息重新构造一个循环。