【问题标题】:Is it possible to do a depth first search iteratively without copying visited nodes?是否可以在不复制访问节点的情况下迭代地进行深度优先搜索?
【发布时间】:2022-11-10 00:26:49
【问题描述】:

背景

  • 我正在二维网格中搜索一个单词。
  • 我们可以左/右和上/下搜索。
  • 例如,在这个网格中,从(0,0) 开始搜索"abef" 将返回True

示例(网格 1):

我在哪里

  • 递归版本给出了预期的结果(见下面的dfs_rec())。
  • 迭代版本也给出了预期的结果(见下面的dfs_iter())。然而,在这个版本中,我将visited 集的副本复制到每个节点的堆栈上。

我的问题是

  • 有没有办法避免在迭代版本中复制 (visited.copy()),并在递归版本中添加/删除单个 visited 集?

更多细节和我尝试过的东西......

  • dfs_rec() 中有一个名为visitedset(),它通过visited.add((row,col))visited.remove((row,col)) 进行了更改

  • 但是在dfs_iter() 中,我每次都将visited.copy() 推入堆栈,以防止节点被错误地标记为已访问。

  • 我已经看到了一些迭代示例,其中他们使用单个 visited 集合,而不复制或从集合中删除任何内容,但这并没有在这些示例中给我正确的输出(请参阅下面的 grid3 使用 grid3)。

例如,以这个网格为例:

  • 假设你搜索"abexxxxxx"(覆盖整个网格),预期输出将是True

  • 但是dfs_iter_nocopy() 将在grid2grid3 之一上给出不正确的输出(它们只是镜像,一个会通过,一个会失败),具体取决于您将节点推入堆栈的顺序。

  • 发生的事情是,当您搜索 "abexxxxxx" 时,它会搜索这样的路径(仅命中 5 个 x,而需要 6 个)。

  • 它将(1,0) 处的x 标记为已访问,当需要搜索该分支时,它会停在(1,0),如下所示:

代码

def width  (g): return len(g)
def height (g): return len(g[0])
def valid (g,r,c): return r>=0 and c>=0 and r<height(g) and c<width(g)

def dfs_rec (grid, word, row, col, visited):

    if not valid(grid, row, col): return False  # (row,col) off board
    if (row,col) in visited:      return False  # already checked
    if grid[row][col] != word[0]: return False  # not right path

    if grid[row][col] == word: # len(word)==1
        return True

    visited.add((row,col))

    if dfs_rec(grid, word[1:], row, col+1, visited): return True
    if dfs_rec(grid, word[1:], row+1, col, visited): return True
    if dfs_rec(grid, word[1:], row, col-1, visited): return True
    if dfs_rec(grid, word[1:], row-1, col, visited): return True

    # Not found on this path, don't block for other paths
    visited.remove((row,col))

    return False

def dfs_iter (grid, start_word, start_row, start_col, start_visited):

    stack = [ (start_row, start_col, start_word, start_visited) ]

    while len(stack) > 0:

        row,col,word,visited = stack.pop()

        if not valid(grid, row, col): continue
        if (row,col) in visited: continue
        if grid[row][col] != word[0]: continue

        if grid[row][col] == word:
            return True

        visited.add((row,col))

        stack.append( (row, col+1, word[1:], visited.copy()) )
        stack.append( (row+1, col, word[1:], visited.copy()) )
        stack.append( (row, col-1, word[1:], visited.copy()) )
        stack.append( (row-1, col, word[1:], visited.copy()) )

    return False

def dfs_iter_nocopy (grid, start_word, start_row, start_col):

    visited = set()
    stack = [ (start_row, start_col, start_word) ]

    while len(stack) > 0:

        row,col,word = stack.pop()

        if not valid(grid, row, col): continue
        if (row,col) in visited: continue
        if grid[row][col] != word[0]: continue

        if grid[row][col] == word:
            return True

        visited.add((row,col))

        stack.append( (row, col+1, word[1:]) )
        stack.append( (row+1, col, word[1:]) )
        stack.append( (row, col-1, word[1:]) )
        stack.append( (row-1, col, word[1:]) )

    return False

if __name__ == '__main__':

    grid  = [ 'abc', 'def', 'hij' ]
    grid2 = [ 'abx', 'xex', 'xxx' ]
    grid3 = [ 'xba', 'xex', 'xxx' ]

    print( dfs_rec(grid, 'abef', 0, 0, set() ) == True   )
    print( dfs_rec(grid, 'abcd', 0, 0, set() ) == False )
    print( dfs_rec(grid, 'abcfjihde', 0, 0, set() ) == True )
    print( dfs_rec(grid, 'abefjihd', 0, 0, set() ) == True )
    print( dfs_rec(grid, 'abefjihda', 0, 0, set() ) == False )
    print( dfs_rec(grid, 'abefjihi', 0, 0, set() ) == False )

    print( dfs_iter(grid, 'abc', 0, 0, set() ) == True   )
    print( dfs_iter(grid, 'abef', 0, 0, set() ) == True   )
    print( dfs_iter(grid, 'abcd', 0, 0, set() ) == False )
    print( dfs_iter(grid, 'abcfjihde', 0, 0, set() ) == True )
    print( dfs_iter(grid, 'abefjihd', 0, 0, set() ) == True )
    print( dfs_iter(grid, 'abefjihda', 0, 0, set() ) == False )
    print( dfs_iter(grid, 'abefjihi', 0, 0, set() ) == False )

    print( dfs_rec(grid2, 'abexxxxxx', 0, 0, set() ) == True   )
    print( dfs_iter(grid2, 'abexxxxxx', 0, 0, set() ) == True   )
    print( dfs_iter_nocopy(grid2, 'abexxxxxx', 0, 0 ) == True   )
    print( dfs_rec(grid3, 'abexxxxxx', 0, 2, set() ) == True   )
    print( dfs_iter(grid3, 'abexxxxxx', 0, 2, set() ) == True   )
    print( dfs_iter_nocopy(grid3, 'abexxxxxx', 0, 2 ) == True   ) # <-- Problem, prints False

【问题讨论】:

  • 您需要将您称为visited.add 的指标推送到您的stack 上。当您弹出该指标时,您需要调用visited.remove

标签: python algorithm graph-theory depth-first-search tree-search


【解决方案1】:

您注意到递归版本能够使用单个 visited 累加器,方法是在回溯时使用 visited.remove((row,col)) 重置它。所以这里可以通过模仿函数调用栈来做同样的事情,这样我们就知道什么时候发生了回溯。

def dfs_iter_nocopy (grid, start_word, start_row, start_col):
    visited = []   # order now matters
    last_depth = 0 # decreases when backtracking 
    stack = [ (start_row, start_col, start_word, last_depth+1) ]

    while len(stack) > 0:
        row, col, word, depth = stack.pop()
        if not valid(grid, row, col): continue
        while last_depth >= depth: # just backtracked
            last_depth -= 1
            visited.pop()          # simulate returning from the call stack
        if (row,col) in visited: continue
        if grid[row][col] != word[0]: continue
        
        if grid[row][col] == word:
            return True

        visited.append((row,col))
        last_depth = depth
        depth += 1 # simulate adding recursive call to the call stack
        stack.append( (row, col+1, word[1:], depth) )
        stack.append( (row+1, col, word[1:], depth) )
        stack.append( (row, col-1, word[1:], depth) )
        stack.append( (row-1, col, word[1:], depth) )
    return False

随着探索新的瓦片,深度会增加,但随着我们耗尽特定路径的可能性并恢复到较早的分叉,深度会减少。这就是我所说的回溯的意思。

编辑:变量名

【讨论】:

    【解决方案2】:

    如果生成的路径有循环,则问题将是如果不将路径扩展到某个状态。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多