基于回溯法的数独求解(Python)

相关介绍

什么是数独?

数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复 。
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
在这里插入图片描述

什么是回溯法?

回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。使用回溯法的核心是记录每一步操作的位置,并在当前位置的数字均不符合条件时退回至上一位置。由此,可以使用栈来实现。

思路及实现

算法思路

首先,从第一行开始寻找第一个待填写的元素位置,并压入用于记录每次操作位置的栈中;然后,判断此时该位置元素是否合法(合法:即该数字位于0-9之间,并且与该数字所在行、所在列、所在宫内的数字均不重复)。当该数字合法时,则继续寻找下一个待填写的位置,并压入栈中;当该数字不合法时,若该数字还没有大于9则自增,若该数字已大于9则退回至上一位置(当前位置归零并弹出);最后,当最后一个待填写的元素合法且不等于0之前,重复执行上述步骤。

Yes
No
Yes
No
Yes
No
最后一个空位置不合法
当前位置合法
输出数独答案
寻找下一元素
当前位置>=9
退回至上一位置
当前位置自增
.

代码编写

设置记录空位置的栈

  • 首先,声明列表。
#全局变量记录空位置(使用回溯法)
indexList = []
  • 然后,实现入栈和出栈的方法。
# --- 入栈 --- #
def myPush(coordinate):
	indexList.append(coordinate)

# --- 出栈 --- #
def myPop():
	indexList.pop()

寻找第一个空元素位置

# --- 寻找第一个空元素位置 --- #
def firstEmpty():
    for i in range(0,9):
        for j in range(0,9):
            if sudoku[i][j] == 0:
                myPush([i,j])
                return

寻找最后一个空元素位置

# --- 寻找最后一个空元素位置 --- #
def lastEmpty():
    for i in range(8,-1,-1):
        for j in range(8,-1,-1):
            if sudoku[i][j] == 0: #'0'
                return [i,j]

位置判空加入栈中

# --- 位置判空加入栈中 --- #
def isEmpty():
    #获取栈顶元素
    a,b = indexList[-1]
    #从下一元素开始找起
    b += 1 
    for i in range(a,9):
        if i != a:
            b = 0
        for j in range(b,9):
            if sudoku[i][j] == 0:
                myPush([i,j])
                return

当前位置自增

# --- 当前位置自增 ---#
def myAdd():
    #获取栈顶元素
    x,y = indexList[-1]
    sudoku[x][y] += 1

判断当前位置是否合法

# --- 判断当前位置是否合法 --- #
def isLegal():
    #获取栈顶元素
    x,y = indexList[-1]
    temp = sudoku[x][y]

    #判断该行是否重复
    for i in range(9):
        if sudoku[x][i] == temp and i != y:
            return False; #当前位置不合法

    #判断该列是否重复
    for i in range(9): 
        if sudoku[i][y] == temp and i != x:
            return False; #当前位置不合法

    #判断该宫是否重复
    xx = int(x / 3)
    yy = int(y / 3)
    for i in range(3):
        for j in range(3):
            if sudoku[xx * 3 + i][yy * 3 + j] == temp and (xx * 3 + i) != x and (yy * 3 + j) != y:
                return False; #当前位置不合法
    return True #当前位置合法

判断某一位置是否合法 (用于判断末尾元素)

# --- 判断某一位置是否合法 --- #
def oneIsLegal(x,y):
    temp = sudoku[x][y]
    #判断该行是否重复
    for i in range(9):
        if sudoku[x][i] == temp and i != y:
            return False; #当前位置不合法

    #判断该列是否重复
    for i in range(9): 
        if sudoku[i][y] == temp and i != x:
            return False; #当前位置不合法

    #判断该宫是否重复
    xx = int(x / 3)
    yy = int(y / 3)
    for i in range(3):
        for j in range(3):
            if sudoku[xx * 3 + i][yy * 3 + j] == temp and (xx * 3 + i) != x and (yy * 3 + j) != y:
                return False; #当前位置不合法
    return True #当前位置合法

回退函数

# --- 回退函数 --- #
def myReturn():
    #获取栈顶元素
    x,y = indexList[-1]
    sudoku[x][y] = 0
    myPop()

输出数独

# --- 输出数独 --- #
def myOutput():
    print("答案为:")
    for i in range(9):
        print(sudoku[i])

主程序

# --- 程序入口 --- #
def main():
    #寻找首个空元素
    firstEmpty()

    #寻找末尾空元素
    last = lastEmpty()
    m,n = last

    #当末尾空元素合法且不等于0时停止循环
    while oneIsLegal(m,n) != True or sudoku[m][n] == 0:
        #获取栈顶元素
        x,y = indexList[-1]

        #判断当前位置是否合法
        if isLegal() == True and sudoku[x][y] <= 9 and 0 < sudoku[x][y]:
            #寻找下一个空元素
            isEmpty()
        else:
            #不合法 - 回退
            if sudoku[x][y] >= 9:
                myReturn()
                myAdd()
            else: #不合法 - 自增
                myAdd()
    myOutput()

if __name__ == "__main__":
    main()

测试数独

#储存数独元素
sudoku = [[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,0,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],
]

运行结果

在这里插入图片描述

完整代码

#全局变量记录空位置(使用回溯法)
indexList = []

#储存数独元素
sudoku = [[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,0,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],
]

# --- 程序入口 --- #
def main():
    #寻找首个空元素
    firstEmpty()
    #寻找末尾空元素
    last = lastEmpty()
    m,n = last
    #当末尾空元素合法且不等于0时停止循环
    while oneIsLegal(m,n) != True or sudoku[m][n] == 0:
        #获取栈顶元素
        x,y = indexList[-1]
        #判断当前位置是否合法
        if isLegal() == True and sudoku[x][y] <= 9 and 0 < sudoku[x][y]:
            #寻找下一个空元素
            isEmpty()
        else:
            #不合法 - 回退
            if sudoku[x][y] >= 9:
                myReturn()
                myAdd()
            else: #不合法 - 自增
                myAdd()
    myOutput()

# --- 寻找第一个空元素位置 --- #
def firstEmpty():
    for i in range(0,9):
        for j in range(0,9):
            if sudoku[i][j] == 0: #'0'
                myPush([i,j])
                return

# --- 寻找最后一个空元素位置 --- #
def lastEmpty():
    for i in range(8,-1,-1):
        for j in range(8,-1,-1):
            if sudoku[i][j] == 0: #'0'
                return [i,j]

# --- 位置判空加入栈中 --- #
def isEmpty():
    #获取栈顶元素
    a,b = indexList[-1]
    #从下一元素开始找起
    b += 1
    for i in range(a,9):
        if i != a:
            b = 0
        for j in range(b,9):
            if sudoku[i][j] == 0: #'0'
                myPush([i,j])
                return

# --- 入栈 --- #
def myPush(coordinate):
    indexList.append(coordinate)

# --- 出栈 --- #
def myPop():
    indexList.pop()

# --- 当前位置自增 ---#
def myAdd():
    #获取栈顶元素
    x,y = indexList[-1]
    sudoku[x][y] += 1

# --- 判断当前位置是否合法 --- #
def isLegal():
    #获取栈顶元素
    x,y = indexList[-1]
    temp = sudoku[x][y]
    #判断该行是否重复
    for i in range(9):
        if sudoku[x][i] == temp and i != y:
            return False; #当前位置不合法
    #判断该列是否重复
    for i in range(9): 
        if sudoku[i][y] == temp and i != x:
            return False; #当前位置不合法
    #判断该宫是否重复
    xx = int(x / 3)
    yy = int(y / 3)
    for i in range(3):
        for j in range(3):
            if sudoku[xx * 3 + i][yy * 3 + j] == temp and (xx * 3 + i) != x and (yy * 3 + j) != y:
                return False; #当前位置不合法
    return True #当前位置合法
    
# --- 判断某一位置是否合法 --- #
def oneIsLegal(x,y):
    temp = sudoku[x][y]
    #判断该行是否重复
    for i in range(9):
        if sudoku[x][i] == temp and i != y:
            return False; #当前位置不合法
    #判断该列是否重复
    for i in range(9): 
        if sudoku[i][y] == temp and i != x:
            return False; #当前位置不合法
    #判断该宫是否重复
    xx = int(x / 3)
    yy = int(y / 3)
    for i in range(3):
        for j in range(3):
            if sudoku[xx * 3 + i][yy * 3 + j] == temp and (xx * 3 + i) != x and (yy * 3 + j) != y:
                return False; #当前位置不合法
    return True #当前位置合法

# --- 回退函数 --- #
def myReturn():
    #获取栈顶元素
    x,y = indexList[-1]
    sudoku[x][y] = 0
    myPop()

# --- 输出数独 --- #
def myOutput():
    print("答案为:")
    for i in range(9):
        print(sudoku[i])

if __name__ == "__main__":
    main()

猜你喜欢

转载自blog.csdn.net/qq_45899597/article/details/112692306