You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python递归数独求解器重复值问题及优化后死循环困境

数独求解器的递归实现问题

我尝试通过递归实现数独求解器,将每个空位的可能值存储在以索引为键、可能值集合为值的字典中。参数b是扁平化处理后的数独棋盘列表,get_row、get_col、get_box方法可正确返回对应行、列、宫的数值。当字典中某一条目的集合仅含单个元素时,将其填入棋盘b;若不存在此类集合,则从可能值中选取一个并启动递归。

初始完整可复现代码

from typing import List

class Sudoku:
    def solve_it(self, board: List[List[str]]) -> None:
        """
        :param board: sudoku board gets modified inplace at .create_board_from_list
        :return: None
        """
        unit_values = []
        for i in range(len(board)):  # 扁平化棋盘为一维列表
            for j in range(len(board[i])):
                unit_values.append(board[i][j])
        self.go_through_recursive(board, unit_values)

    def get_row(self, n: int, b):  # 获取第n行的所有元素
        assert 0 <= n <= 8
        return set(b[9 * n: 9 * (n + 1)])

    def get_col(self, n: int, b):  # 获取第n列的所有元素
        assert 0 <= n <= 8
        return set(b[n::9])

    def get_box(self, n: int, b):  # 获取第n宫的所有元素
        assert 0 <= n <= 8
        return set(b[i] for i in range(81) if (i // 27) == (n // 3) and i % 9 // 3 == n % 3)

    def go_through_recursive(self, board, b, d=False):
        """
        :param board: 二维矩阵形式的数独棋盘
        :param b: 一维列表形式的数独棋盘
        """
        numbers = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}
        while True:

            if d:
                return True

            missing_dict = {}  # 填充空位的可能值字典
            for idx in range(len(b)):
                if b[idx] == '.':
                    row, col, box = idx // 9, idx % 9, (idx // 27) * 3 + (idx % 9) // 3
                    # 合并当前行、列、宫中已存在的数值
                    values = self.get_row(row, b).union(self.get_col(col, b)).union(self.get_box(box, b))
                    values.remove('.')
                    missing_ones = numbers.difference(values)  # 当前空位的可能值
                    if len(missing_ones) == 0:  # 无可行值,当前路径无效
                        return False
                    missing_dict[idx] = missing_ones

            old_count = b.count('.')
            # 遍历字典,填入唯一可能值
            for idx, missings in missing_dict.items():
                if len(missings) == 1:
                    b[idx] = missings.pop()

            if b.count('.') == 0:  # 棋盘已填满
                self.create_board_from_list(board, b)
                return True

            if b.count('.') == old_count:  # 无进展,启动递归尝试
                for idx, s in missing_dict.items():
                    for number in s:
                        if d:
                            return True
                        bb = b[:]
                        bb[idx] = number
                        d = self.go_through_recursive(board, bb)

    def create_board_from_list(self, board, b):
        temp_board = []
        chunk = 9
        for idx in range(0, len(b), chunk):
            temp_board.append(b[idx: idx + chunk])
        for idx in range(len(board)):
            board[idx] = temp_board[idx]
        print('done')

问题现象

求解完成后,棋盘的行、列或宫中存在重复数值。排查发现,在计算空位可能值时,偶尔会返回全量的1-9集合,这可能是导致数值错误覆盖的原因:

row, col, box = idx // 9, idx % 9, (idx // 27) * 3 + (idx % 9) // 3
values = self.get_row(row, b).union(self.get_col(col, b)).union(self.get_box(box, b))
values.remove('.')  # 有时得到的集合仅包含少量元素,但偶尔会返回全量1-9

测试输入棋盘

board = 
[[".",".","9","7","4","8",".",".","."],
 ["7",".",".",".",".",".",".",".","."],
 [".","2",".","1",".","9",".",".","."],
 [".",".","7",".",".",".","2","4","."],
 [".","6","4",".","1",".","5","9","."],
 [".","9","8",".",".",".","3",".","."],
 [".",".",".","8",".","3",".","2","."],
 [".",".",".",".",".",".",".",".","6"],
 [".",".",".","2","7","5","9",".","."]]

正确输出

board =
[["5","1","9","7","4","8","6","3","2"],
 ["7","8","3","6","5","2","4","1","9"],
 ["4","2","6","1","3","9","8","7","5"],
 ["3","5","7","9","8","6","2","4","1"],
 ["2","6","4","3","1","7","5","9","8"],
 ["1","9","8","5","2","4","3","6","7"],
 ["9","7","5","8","6","3","1","2","4"],
 ["8","3","2","4","9","1","7","5","6"],
 ["6","4","1","2","7","5","9","8","3"]]

代码错误输出

board = 
[["3","1","9","7","4","8","6","5","2"],
 ["7","8","5","6","3","2","1","1","9"],
 ["4","2","6","1","5","9","8","7","3"],
 ["5","3","7","9","8","6","2","4","1"],
 ["2","6","4","3","1","7","5","9","8"],
 ["1","9","8","5","2","4","3","6","7"],
 ["9","7","1","8","6","3","4","2","5"],
 ["8","5","2","4","9","1","7","3","6"],
 ["6","4","3","2","7","5","9","8","4"]]

编辑说明

修改了go_through_recursive方法,将唯一可能值直接填入棋盘,此时字典中仅存在长度为2及以上的集合,但修改后代码陷入无限循环:

def go_through_recursive(self, board, b, d=False):
    """
    :param board: 二维矩阵形式的数独棋盘
    :param b: 一维列表形式的数独棋盘
    """
    numbers = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}
    while True:

        old_count = b.count('.')
        missing_dict = {}  # 填充空位的可能值字典
        for idx in range(len(b)):
            if b[idx] == '.':
                row, col, box = idx // 9, idx % 9, (idx // 27) * 3 + (idx % 9) // 3
                # 合并当前行、列、宫中已存在的数值
                values = self.get_row(row, b).union(self.get_col(col, b)).union(self.get_box(box, b))
                values.remove('.')
                missing_ones = numbers.difference(values)  # 当前空位的可能值
                if len(missing_ones) == 0:  # 无可行值,当前路径无效
                    return False
                elif len(missing_ones) == 1:
                    b[idx] = missing_ones.pop()
                else:
                    missing_dict[idx] = missing_ones

        if b.count('.') == 0:  # 棋盘已填满
            self.create_board_from_list(board, b)
            return True

        if b.count('.') == old_count:  # 无进展,启动递归尝试
            for idx, s in missing_dict.items():
                for number in s:
                    bb = b[:]
                    bb[idx] = number
                    if self.go_through_recursive(board, bb):
                        return True

内容的提问来源于stack exchange,提问作者Fabio Olivetto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:44:56