原创文章,转载请注明出处
“数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。” 1
比如这个数独,左上角有1个9,则红线标出的位置都不能再填9。
总的来说,数独是一个逻辑推理的数字游戏,规则简单,上手容易,逼格高端,提神健脑,是防痴呆、防迟钝的不二神器,应该勤做多练。但是本人是甘于懒惰的,所以写一个解法,让机器自动解数独。
深度优先搜索,回溯搜索算法。
使用4个List,List1(9*9)按位置记录填入数独的数字,List2记录所有当前空白格的坐标,List3(9*9*9)按位置记录该位置1-9这9个数字是否可填(值为该行、该列和该宫某一数字出现的次数,比如List3[ 1 ][ 2 ][ 5 ]=3,表示第1行和第2列和(1,2)这一格所在宫共有3个5,值大于0表示该数字被占用不能填入,值等于0表示该数字未被占用可以填入),List4作为栈缓存每一次遇到分支时的List1、List2和List3。
循环执行1、2步,遇到无尝试解时,则从List4取最后一步状态,重新继续循环,循环跳出条件为List2为空。
import numpy as np
from copy import deepcopy
import time
import xlrd
import xlwt
StartTime=time.time()
wb=xlrd.open_workbook(r'C:\Users\Administrator\Desktop\Python\Sudoku.xlsx')
sheet=wb.sheets()[0]
input_tmp=[]
steps=0 #记录总步数
sudoku=[] #记录填写数字
sudoku_origin=[] #记录原始数字
SudokuRecord=[] #记录尝试记录
blanks_list=[] #记录空白格坐标
SudokuNum=[[[0 for x in range(9)] for y in range(9)] for z in range(9)] #记录每格可用数字
for m in range(1,10):
for n in range(1,10):
if sheet.cell(m,n).value=='':
input_tmp.append(0)
blanks_list.append((m-1,n-1))
else:
input_tmp.append(int(sheet.cell(m,n).value))
sudoku.append(input_tmp)
input_tmp=[]
sudoku_origin=deepcopy(sudoku)
print(np.array(sudoku),'\n')
def Occupy(i,j,num,mType): #占位和清除占位
global SudokuNum
global blanks_list
for x in range(0,7,3): #查找所在宫左上角坐标
if i>=x:
m=x
if j>=x:
n=x
for x in range(9): #同行同列
if mType==1:
SudokuNum[i][x][num-1]+=1 #本行占位+1
elif mType==2:
SudokuNum[i][x][num-1]-=1 #本行占位-1
if x!=i:
if mType==1:
SudokuNum[x][j][num-1]+=1 #本列占位+1
elif mType==2:
SudokuNum[x][j][num-1]-=1 #本列占位-1
for x in range(3): #所在宫
for y in range(3):
if m+x!=i and n+y!=j:
if mType==1:
SudokuNum[m+x][n+y][num-1]+=1
elif mType==2:
SudokuNum[m+x][n+y][num-1]-=1
for m in range(9):
for n in range(9):
if sudoku[m][n]!=0:
Occupy(m,n,sudoku[m][n],1)
def CordsRecord(i,j):
global blanks_list
for x in range(len(blanks_list)):
if blanks_list[x][0]==i and blanks_list[x][1]==j:
return x
break
def Unique(): #唯一解
global blanks_list
global SudokuNum
global steps
flag=False
cords_list=[] #记录要删除的空白坐标下标
for cord in blanks_list:
if SudokuNum[cord[0]][cord[1]].count(0)==1:
for x in range(9):
if SudokuNum[cord[0]][cord[1]][x]==0:
sudoku[cord[0]][cord[1]]=x+1
Occupy(cord[0],cord[1],x+1,1)
cords_list.append(CordsRecord(cord[0],cord[1]))
flag=True
steps+=1
break
for x in reversed(range(len(cords_list))):#倒序删
blanks_list.pop(cords_list[x])
return flag
def MinProb():
global blanks_list
num=10
for x in blanks_list:
if SudokuNum[x[0]][x[1]].count(0)<num:
num=SudokuNum[x[0]][x[1]].count(0)
cord=x
return cord
def Test(): #尝试解
global steps
flag=False
cord=MinProb()
for x in range(9):
if SudokuNum[cord[0]][cord[1]][x]==0:
SudokuNum_temp=deepcopy(SudokuNum)
SudokuNum_temp[cord[0]][cord[1]][x]+=1 #剪枝
SudokuRecord.append([deepcopy(sudoku),deepcopy(SudokuNum_temp),deepcopy(blanks_list)]) #尝试记录,深拷贝,压入栈
sudoku[cord[0]][cord[1]]=x+1
Occupy(cord[0],cord[1],x+1,1)
blanks_list.pop(CordsRecord(cord[0],cord[1]))
flag=True
steps+=1
break
return flag
while len(blanks_list)!=0:
while Unique():
pass
if len(blanks_list)==0:
break
if Test():
pass
else:
if len(SudokuRecord)==0:
print('无解!')
break
else:
#弹出栈
sudoku=deepcopy(SudokuRecord[-1][0])
SudokuNum=deepcopy(SudokuRecord[-1][1])
blanks_list=deepcopy(SudokuRecord[-1][2])
SudokuRecord.pop(-1)
EndTime=time.time()
print('总尝试步数为:%d' % steps)
print('运行时间:%s' % (EndTime-StartTime))
print(np.array(sudoku))
我这里后面还有一个将结果写入Excel表并美化的
wb=xlwt.Workbook(encoding='utf8')
sheet=wb.add_sheet('Sudoku')
font1=xlwt.Font()
font2=xlwt.Font()
font1.colour_index=0
font2.colour_index=2
borders=xlwt.Borders()
borders.left = 1
borders.right = 1
borders.top = 1
borders.bottom = 1
borders.bottom_colour=0x3A
al = xlwt.Alignment()
al.horz=0x02
al.vert = 0x01
style1=xlwt.XFStyle()
style2=xlwt.XFStyle()
style1.borders=borders
style1.font=font1
style1.alignment = al
style2.borders=borders
style2.font=font2
style2.alignment=al
for i in range(1,10):
sheet.col(i).width=35*20
for i in range(1,10):
for j in range(1,10):
if sudoku_origin[i-1][j-1]==0:
sheet.write(i,j,sudoku[i-1][j-1],style2)
else:
sheet.write(i,j,sudoku[i-1][j-1],style1)
wb.save(r'C:\Users\Administrator\Desktop\SudokuSolve.xls')
下图是号称世界上最难的数独,是由芬兰数学家因卡拉花费3个月时间设计出来的
程序运行结果如下:
Python: 3.6.6
IDE: Anaconda3
https://baike.baidu.com/item/数独/74847?fr=aladdin ↩︎