是否可以在不复制访问节点的情况下迭代地进行深度优先搜索?

Is it possible to do a depth first search iteratively without copying visited nodes?

背景

示例(网格 1):

我在哪里

我的问题是

进一步的细节和我尝试过的东西...

以这个网格为例:

代码

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.remove((row,col)) 重置它来使用单个 visited 累加器。所以这里也可以通过模拟函数调用栈来做同样的事情,这样我们就知道什么时候回溯了。

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

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

编辑:变量名