0
点赞
收藏
分享

微信扫一扫

回溯法的应用:数独


我之前做安卓课程设计找到课本上有一个数独游戏,当时玩的时候发现太费时间了,打算编写一个算法专门用来解数独,可是之前一直忘了这事,现在才想起来。



概述




在解数独之前首先说一下什么是数独,数独就是一个 9*9 的格子,每一个格子是数字 1~9 中的任意一个,要确保其所在的行,所在的列,所在的块(每个 3*3 的块,这样的块一共有 9 个)中都没有重复的数字。

解数独的方法我们首先能够想到的应该就是回溯法吧,没冲突就填上,填到半路发现没法填了就回溯。下面来说一下回溯法解数独的具体步骤。

  1. 获取数独的最初状态。
  2. 找到第一个未填项,填入没有冲突的数字,当填完第 i 个未填项之后,第 i+1 个未填项无法填入数字时,回溯到第 i 个,重复之前的操作,直到全部填完。

是不是很简单?为了把数据和基于数据的操作封装在一起,依旧使用面向对象来实现。



初始化




在这个算法中,我们需要获取数独的初始状态,数独的初始状态很简单,一个 9 行 9 列的二维数组,其中未填项是 0。我们直接把这个二维数组作为参数赋值给数独类的实例的属性即可。

def __init__(self, initial_state):    """    初始化    :param initial_state: 初始状态    """    self.state = initial_state


检测冲突




在编写解数独的算法之前,我们首先需要一个方法来检测当前位置的当前值是不是有冲突,这个算法很简单,如果其所在的行,所在的列,所在的块中有重复的数字,就有冲突,否则没有冲突。

def conflict(self, row, column, value):    """    检测冲突    :param row: 当前值所在的行    :param column: 当前值所在的列    :param value: 当前值    :return: 有冲突返回 True,无冲突返回 False    """    for item in self.state[row]:        if item == value:            return True    for row0 in self.state:        if row0[column] == value:            return True    row0, column0 = row//3*3, column//3*3    block = self.state[row0][column0:column0+3]+self.state[row0+1][column0:column0+3] + \
self.state[row0+2][column0:column0+3] for item in block: if item == value: return True return False



获取下一个未填项




获取下一个未填项就是获取二维数组中第一个元素为 0 的行和列,如果没有元素为 0,就返回两个 -1(正常的情况下,返回两个值——行和列,如果在这里返回一个值可能会出现解包错误)。

def get_next(self, row, column):    """    获取下一个未填项    :param row: 当前行    :param column: 当前列    :return: 下一个未填项的行,下一个未填项的列    """    for next_column in range(column+1, 9):        if self.state[row][next_column] == 0:            return row, next_column
for next_row in range(row+1, 9): for next_column in range(9): if self.state[next_row][next_column] == 0: return next_row, next_column
return -1, -1


算当前未填项




如果当前项是 0,也就是需要给它填上一个非 0 数字,填上的数字要和其所在的行,所在的列,所在的块没有冲突,如果有冲突换一个数,填到半路没法继续填就回溯。

def solve(self, row, column):    """    解当前未填项    :param row: 当前未填项所在的行    :param column: 当前未填项所在的列    :return: 如果填满就返回 True,否则递归直到填满    """    if self.state[row][column] == 0:        for value in range(1, 10):            if not self.conflict(row, column, value):                self.state[row][column] = value
next_row, next_column = self.get_next(row, column) if next_row == -1: return True else: end = self.solve(next_row, next_column) if not end: self.state[row][column] = 0 else: return True


运行整个算法




运行整个算法很简单,找到第一个未填项进行填写就行了。

def run(self):    """    运行整个算法    :return:    """    if self.state[0][0] == 0:        self.solve(0, 0)    else:        row, column = self.get_next(0, 0)        self.solve(row, column)

下面直接给出整个算法的源代码,测试这个算法使用的是芬兰数学家因卡拉花费3个月时间设计出的世界上迄今难度最大的数独。

class Sudoku:    def __init__(self, initial_state):        """        初始化        :param initial_state: 初始状态        """        self.state = initial_state

def conflict(self, row, column, value): """ 检测冲突 :param row: 当前值所在的行 :param column: 当前值所在的列 :param value: 当前值 :return: 有冲突返回 True,无冲突返回 False """ for item in self.state[row]: if item == value: return True for row0 in self.state: if row0[column] == value: return True row0, column0 = row//3*3, column//3*3 block = self.state[row0][column0:column0+3]+self.state[row0+1][column0:column0+3] + \
self.state[row0+2][column0:column0+3] for item in block: if item == value: return True return False
def get_next(self, row, column): """ 获取下一个未填项 :param row: 当前行 :param column: 当前列 :return: 下一个未填项的行,下一个未填项的列 """ for next_column in range(column+1, 9): if self.state[row][next_column] == 0: return row, next_column
for next_row in range(row+1, 9): for next_column in range(9): if self.state[next_row][next_column] == 0: return next_row, next_column
return -1, -1

def solve(self, row, column): """ 解当前未填项 :param row: 当前未填项所在的行 :param column: 当前未填项所在的列 :return: 如果填满就返回 True,否则递归直到填满 """ if self.state[row][column] == 0: for value in range(1, 10): if not self.conflict(row, column, value): self.state[row][column] = value
next_row, next_column = self.get_next(row, column) if next_row == -1: return True else: end = self.solve(next_row, next_column) if not end: self.state[row][column] = 0 else: return True

def run(self): """ 运行整个算法 :return: """ if self.state[0][0] == 0: self.solve(0, 0) else: row, column = self.get_next(0, 0) self.solve(row, column)


if __name__ == '__main__': state = [[8, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 3, 6, 0, 0, 0, 0, 0], [0, 7, 0, 0, 9, 0, 2, 0, 0], [0, 5, 0, 0, 0, 7, 0, 0, 0], [0, 0, 0, 8, 4, 5, 7, 0, 0], [0, 0, 0, 1, 0, 0, 0, 3, 0], [0, 0, 1, 0, 0, 0, 0, 6, 8], [0, 0, 8, 5, 0, 0, 0, 1, 0], [0, 9, 0, 0, 0, 0, 4, 0, 0]] sudoku = Sudoku(state) sudoku.run() for row1 in sudoku.state: print(row1)

运行结果如图所示。

回溯法的应用:数独_数独

今天的文章有不懂的可以后台回复“加群”,备注:小陈学Python,不备注可是会被拒绝的哦~!

扫描二维码

获取更多精彩

小陈学 Python

回溯法的应用:数独_初始化_02




举报

相关推荐

0 条评论