数独
描述
数独是一项非常简单的任务。如图所示,将 9 行 9 列的方桌分成 9 个 3x3 的小正方形。在某些单元格中,写入从 1 到 9 的十进制数字。其他单元格为空。目标是用从 1 到 9 的十进制数字填充空单元格,每个单元格一位数字,这样在每一行、每列和每个标记的 3x3 子方块中,出现从 1 到 9 的所有数字。编写一个程序来解决给定的数独任务。
输入
输入数据将从测试用例的数量开始。对于每个测试用例,后面有 9 行,对应于表的行。在每一行上给出一个正好由 9 个十进制数字组成的字符串,对应于此行中的单元格。如果单元格为空,则用 0 表示。
输出
对于每个测试用例,程序应以与输入数据相同的格式打印解决方案。必须根据规则填充空单元格。如果解决方案不是唯一的,则程序可以打印其中任何一个。
示例输入
1 103000509 002109400 000704000 300502006 060000050 700803004 000401000 009205800 804000107
示例输出
143628579 572139468 986754231 391542786 468917352 725863914 237481695 619275843 854396127
数独,用DFS,其中最需要注意的便是剪枝
1.一个9宫格内不能有相同的数字
2.一行不能有相同的数字
3.一列不能有相同的数字
(有一种暴力的思路,就是对每一个0的地方判断是否只有一种情况,如果只有一种情况,就填入那个数字,这样的时间复杂度应该是9 * 9 * 9 ** 2)
回溯算法
def solve_sudoku(board): """ 使用回溯算法解决数独问题 """ def is_valid(row, col, num): """ 检查填入的数字是否符合数独规则 """ for i in range(9): # 检查该行是否有相同数字 if board[row][i] == num: return False # 检查该列是否有相同数字 if board[i][col] == num: return False # 检查该九宫格是否有相同数字 if board[3*(row//3)+i//3][3*(col//3)+i%3] == num: return False return True def backtrack(row, col): """ 回溯算法主函数 """ # 搜索到最后一行的下一行,说明已找到一个可行解 if row == 9: return True # 搜索当前行的下一列 next_row = row if col < 8 else row+1 #妙啊!!!如果列读完就换行 next_col = (col+1) % 9 # 如果当前位置已经填入数字,则跳过 if board[row][col] != '0': return backtrack(next_row, next_col) # 尝试填入数字 for num in range(1, 10): if is_valid(row, col, str(num)): board[row][col] = str(num) if backtrack(next_row, next_col): return True board[row][col] = '0' # 无解,回溯 return False # 将输入的数独字符串转换为二维列表 board = [list(row) for row in board] # 调用回溯算法 backtrack(0, 0) # 将二维列表转换为数独字符串并返回 return [''.join(row) for row in board]
这个代码还是有很多值得学习的地方的:
1.写一个is_valid函数,然后传入参数为row, col, num
直接去比较每一行每一列则是使用board[i][col]
还有board[row][i]
来遍历,还有九宫格里面的变换,board[3*(row//3)+i//3][3*(col//3)+i%3]
来遍历,3*(row//3)
就是行的开始,然后0~9
每3个row的地方加上一i//3
,而col的地方则是3个一循环i%3
,也是3*(col//3)
从头开始,这很巧妙。只要发现有雷同的就返回False
2.backtrack
函数中传入两个参数,一个row,一个col,妙的地方在于对next_row
,next_col
的处理
next_row = row if col < 8 else row+1
next_col = (col+1) % 9
next_row
里用了赋值里面嵌套if,如果列的地方大于9则说明要换行了(因为索引从0开始所以这里是小于8),if col < 8
next_row = row
, else row + 1
next_col
直接模9
3.if board[row][col] != '0':
return backtrack(next_row, next_col) # 如果当前位置已经填入数字,则跳过
这里跳过不仅仅是continue,而是直接调用了backtrack,在return里面调用函数!
4.if backtrack(next_row, next_col):
return True
这里需要注意什么情况下会返回True
给他,
if row == 9:
return True
我们看到只有这里,那么当整个代码结束后就会一路True下去
5.当然如果num里面都不符合,那么return False, 即回溯,在
if backtrack(next_row, next_col):
return True
这下方加上board[row][col] = '0'
还原状态
6.return [''.join(row) for row in board]
这个是什么操作呢?
return [''.join(row) for row in board]
是将数独的解从二维列表转换为字符串的操作。
在solve_sudoku()
函数中,我们将输入的数独字符串转换为一个由列表表示的二维棋盘,其中每个空格用'.'表示。在回溯算法求解完毕后,我们需要将二维棋盘转换为数独字符串,以便输出答案。
[''.join(row) for row in board]
是一个列表推导式,它遍历二维列表board
中的每一行,将该行中的字符拼接成一个字符串,并将该字符串添加到一个新的列表中。最终,该列表中的每个元素都是一个由字符串表示的数独行,将它们连接起来即可得到完整的数独字符串。
完整代码
def solve_sudoku(board): """ 使用回溯算法解决数独问题 """ def is_valid(row, col, num): """ 检查填入的数字是否符合数独规则 """ for i in range(9): # 检查该行是否有相同数字 if board[row][i] == num: return False # 检查该列是否有相同数字 if board[i][col] == num: return False # 检查该九宫格是否有相同数字 if board[3 * (row // 3) + i // 3][3 * (col // 3) + i % 3] == num: return False return True def backtrack(row, col): """ 回溯算法主函数 """ # 搜索到最后一行的下一行,说明已找到一个可行解 if row == 9: return True # 搜索当前行的下一列 next_row = row if col < 8 else row + 1 # 妙啊!!!如果列读完就换行 next_col = (col + 1) % 9 # 如果当前位置已经填入数字,则跳过 if board[row][col] != '0': return backtrack(next_row, next_col) # 尝试填入数字 for num in range(1, 10): if is_valid(row, col, str(num)): board[row][col] = str(num) if backtrack(next_row, next_col): return True board[row][col] = '0' # 无解,回溯 return False board = [list(row) for row in board] # 调用回溯算法 backtrack(0, 0) # 将二维列表转换为数独字符串并返回 for i in range(9): for j in range(9): print(board[i][j], end = "") print("") return [''.join(row) for row in board] if __name__ == '__main__': # 将输入的数独字符串转换为二维列表 board = [[] for i in range(9)] for i in range(9): board[i] = input() solve_sudoku(board)