【问题标题】:DFS - Implementing a O(|E|) timing complexity solutionDFS - 实现 O(|E|) 时序复杂度解决方案
【发布时间】:2015-07-17 05:55:35
【问题描述】:

我正在尝试实现 DFS,但是,根据 A StackOverFlow link about DFS,DFS 仅使用堆栈。

这是我当前的实现:

def DFS_graph_recursive(graph, start, end, stack):
    #Stack to push the node when we are entering the node
    stack.append(start)
    while(end not in stack):
        for i in graph.edges[start]:
            #check if this is inside the stack, to make sure that it doesn't try to go back
            if (i not in stack):
                #Recursive Call
                DFS_graph_recursive(graph, i, end, stack)
        #Pop the stack when we are leaving this node, but need to make sure if end is inside
    if(end not in stack):
        stack.pop()

有几个问题,时间复杂度看起来不像 O(|E|)。第一个while循环是O(|E|),但是,我需要检查我的下一个节点是否已经在堆栈中,以避免返回,如果这个堆栈很大,我假设i not in stack需要O( n) 进行计算,使得该算法在复杂度上看起来像 O(|E|^2)。

if(end not in stack),使算法在时间复杂度方面变得更糟,因为对于每次边搜索,它都需要检查 end 是否在堆栈中,这意味着如果我们找到了解决方案,我们不想弹出堆栈.这进一步将时间复杂度增加到 O(|E|^2 + |E|)。有没有办法只用一个while循环来做到这一点?

就空间复杂度而言,最坏的情况应该是 O(|V|) 我们有一棵分支因子为 1 的长树。

如果它是一种二叉树类型,我可以轻松实现 O(|E|) 解决方案,它具有向下到子节点的有向路径。问题是,由于问题是无向图,假设有两个顶点,A 和 B。如果 A 连接到 B,则 B 连接到 A。所以如果我不检查if (i not in stack),我会被卡住从A到B,B到A...等来回

【问题讨论】:

  • 递归调用不需要栈
  • 我没有,但我需要跟踪路径,而且它保证它不会让我在两个节点之间来回走动。
  • 您打算如何跟踪路径?如果你真的想要,你可以将遇到的节点推送到一个全局列表中。
  • 堆栈基本上是跟踪路径。全局路径不是正确的方法,因为时间复杂度没有改变。
  • 在算法节点的“标准教科书描述”中有一个与之相关的color。在运行算法之前,您首先将它们标记为白色,然后在将它们从队列中弹出时将它们标记为灰色。当您完成某个节点的循环时,您将其标记为黑色。每当您从队列中弹出一个节点时,您都​​会检查它的颜色。如果它不是白色的,您已经访问过它,您可以忽略它。如果它是灰色的,你就知道你找到了一个循环。检查颜色显然是 O(1) 并且标记所有节点只增加 O(|V|) 所以复杂度不会增加。

标签: python algorithm graph-algorithm


【解决方案1】:

使用此算法,您实际上最终会多次访问同一个节点(i not in stack 的复杂性是一个额外的问题)

您应该考虑保留visited 列表/字典,以跟踪您访问过的节点。如果您访问过该节点,那么您将不会将其推入堆栈。代码就像 -

vis = {}
def DFS_graph(graph, start, end, stack):
    #Stack to push the node when we are entering the node
    stack.append(start)
    vis[start] = 1
    while len(stack):
        # got to an element
        found_element = stack[-1]
        if found_element == end:
            # you found the end, so break now
            break
        stack.pop()
        for i in graph.edges[start]:
            #check if this is inside the stack, to make sure that it doesn't try to go back
            if i not in vis:
                #Recursive Call
                stack.push(i)

这里我使用了一个字典,它的实际复杂度在最坏情况下应该是 O(n),在平均情况下应该是 O(1)。 如果您将图形节点表示为 g[0], g[1], g[2] ....,那么您可以使用列表将复杂度变为 O(1) 最坏情况。

【讨论】:

  • 有没有办法在没有访问过的列表/字典的情况下做到这一点?你不会增加空间复杂度吗?原来是 O(|E|) 的堆栈,但现在看起来像 O(|E|+|V|),因为您必须为访问的节点保留额外的空间。
  • @user1157751 您会将顶点或节点推入堆栈而不是边缘。所以最坏情况的空间复杂度是 O(|V|)(对于被访问的数组) + O(|V|)(对于栈) = O(|V|)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-13
  • 1970-01-01
  • 2015-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-13
  • 1970-01-01
相关资源
最近更新 更多