【问题标题】:how is this code working? backtracking and recursion这段代码是如何工作的?回溯和递归
【发布时间】:2021-11-16 22:47:08
【问题描述】:

这是解决数独的有效代码:

def is_valid(board, row, col, num):
    for i in range(9):
        if board[row][i] == num:
            return False

    for i in range(9):
        if board[i][col] == num:
            return False

    box_row = (row - row % 3)
    box_col = (col - col % 3)

    for i in range(3):
        for j in range(3):
            if board[box_row + i][box_col + j] == num:
                return False
    return True

def solve(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == 0:
                for num in range(1,10):
                    if is_valid(board, row, col, num):
                        board[row][col] = num
                        solve(board)
                        board[row][col] = 0

                return False

print(np.matrix(board))

solve(board)

让我困惑的部分是:

if board[row][col] == 0:
    for num in range(1,10):
        if is_valid(board, row, col, num):
            board[row][col] = num
            solve(board)
            board[row][col] = 0

    return False

这部分是如何工作的?我知道它会将数字分配给当前行和列 THEN,再次运行solve()函数,程序什么时候运行:

board[row][col] = 0

因为据我了解,除非已经有 0,否则代码不会运行。然后程序将检查该号码是否有效。 另外如果没有有效的num [1~9],它不会返回false并退出函数吗?

谈论它让我头晕目眩,我知道这甚至很难解释,我用谷歌搜索了它。

编辑:

这是我正在处理的董事会:

board_1 = [
    [3, 0, 6, 5, 0, 8, 4, 0, 0],
    [5, 2, 0, 0, 0, 0, 0, 0, 0],
    [0, 8, 7, 0, 0, 0, 0, 3, 1],
    [0, 0, 3, 0, 1, 0, 0, 8, 0],
    [9, 0, 0, 8, 6, 3, 0, 0, 5],
    [0, 5, 0, 0, 9, 0, 6, 0, 0],
    [1, 3, 0, 0, 0, 0, 2, 5, 0],
    [0, 0, 0, 0, 0, 0, 0, 7, 4],
    [0, 0, 5, 2, 0, 6, 3, 0, 0]
    ]

输出:

[[3 1 6 5 7 8 4 9 2]
 [5 2 9 1 3 4 7 6 8]
 [4 8 7 6 2 9 5 3 1]
 [2 6 3 4 1 5 9 8 7]
 [9 7 4 8 6 3 1 2 5]
 [8 5 1 7 9 2 6 4 3]
 [1 3 8 9 4 7 2 5 6]
 [6 9 2 3 5 1 8 7 4]
 [7 4 5 2 8 6 3 1 9]]

【问题讨论】:

  • @jarmod 我刚刚做了,谢谢
  • 如其所写,solve 要么返回 False 要么返回 None 并且不打印任何内容。我怀疑print 应该缩进到某种程度?可能是 1 级缩进?
  • 能分享一下print语句的输出吗?那个显示了董事会以前的样子。我怀疑空字段用 0 表示,在这种情况下会有很多零。

标签: python recursion backtracking recursive-backtracking


【解决方案1】:

solve 函数返回有两种情况。我将重写代码以使其更甜(但你的也可以)*:

def solve(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == 0:
                for num in range(1,10):
                    if is_valid(board, row, col, num):
                        board[row][col] = num
                        solve(board)
                        board[row][col] = 0

                return False
    return True

因此,只要solve 方法到达return 语句之一,它就会返回调用它的方法。请注意,在求解调用之外有两个 if 语句。这些可以评估为False。如果他们经常评估为False,则将达到return 语句之一。这就是你如何到达board[row][col] = 0

如果您想查看已解决的数独,您也必须在最后调用solve 后重复print(np.matrix(board))


*旁注:

  • 正如您所写,solve 方法可以返回NoneFalse,这是不好的风格,因为bool(None) 的计算结果也为False。为什么返回None?那是因为函数末尾没有return 语句等同于return None
  • 你也可以只使用return,除非你以后需要True/False。那是因为solve(board) 没有分配给任何东西。
  • 如果数独没有解决方案,您将进入一个复杂的无限循环,该循环只有在达到最大递归深度时才会结束(在 CPython 中)。

【讨论】:

  • 我刚刚编辑了帖子并将输出与原始板一起发布。我开始掌握它,实际上我尝试的 return False 在这种情况下是不需要的。所以我的问题是。当他到达返回时程序“去”哪里?开始还是如您所说的那样调用它的方法?谢谢
  • 当一个函数到达一个返回值时,它就完成了。所以代码在函数调用之后继续。那是在调用它的函数的中间。你的 Python 解释器会记住函数嵌套了多少次。也许如果您在solve(board) 之后的脚本末尾重复您的打印语句,您就会明白。正如 SpoonMeiser 所说,您也可以将它放在函数内的任何位置。也许与您可以从time 模块中获得的sleep(1) 一起减慢速度。 (您可以使用 Ctrl+C 停止程序)
  • 哦嵌套函数。现在都点击了,谢谢!我现在可以睡得很舒服了
【解决方案2】:

假设问题中的代码(在撰写本文时)存在缩进错误,求解器如下所示:

def solve(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == 0:
                # ignore the code here for now

                return False

    print(np.matrix(board))

暂时忽略我用注释替换的代码是做什么的,这个函数的作用是取一个棋盘,遍历每一个方格,如果有则返回False,否则打印棋盘.

即如果板子已经解决,打印出来,如果没有解决,返回False。

False 返回的事实无关紧要;也可以是return

所以,对于被注释掉的代码。这样做是对找到的每个 0,尝试用每个有效数字替换它,然后递归地尝试解决它。​​

        if board[row][col] == 0:
            for num in range(1,10):
                if is_valid(board, row, col, num):
                    board[row][col] = num
                    solve(board)
                    board[row][col] = 0

因此,如果它是板上唯一的 0,那么如果它找到一个有效数字,则调用 solve 将导致板被打印出来。

如果没有有效的数字,那么在某个时刻插入棋盘的数字之一是不正确的;我们不会递归调用solve,而是继续调用return

因此,对solve 的递归调用可能会也可能不会找到有效的解决方案,代码并不假设只存在一个解决方案,并且在任何一种情况下都会继续查找。它需要撤消它所做的事情,因为它仅在先前递归调用选择的上下文中有效。因此将正方形设置回 0。

【讨论】:

  • 对我来说越来越清楚了,如果你能澄清一件事,那就更清楚了。你说“它的作用是对找到的每个 0,尝试用每个有效数字替换它,然后递归地尝试解决它。​​” - 我的想法是,如果有效,请尝试 1,转到下一个框,如果没有有效数字,请返回并将 1 更改为另一个有效输入。我的理解对吗?或者它同时完成所有的可能性?谢谢!
  • 说 1 和 2 都有效,它会尝试 1,然后无论 1 是否导致实际解决方案,它都会尝试 2;如果你通过一个空板,它会打印所有可能的解决方案。找到一个有效的解决方案不会停止算法。这有帮助吗?
  • 非常喜欢!谢谢!
猜你喜欢
  • 2019-11-23
  • 2012-08-31
  • 2015-10-26
  • 2021-01-27
  • 1970-01-01
  • 2021-04-12
  • 2023-01-30
  • 2011-05-16
  • 2014-05-10
相关资源
最近更新 更多