破解数独——回溯法

博客围绕用回溯法破解数独展开,提及了数独样例,回溯法是解决数独问题的关键算法,在信息技术领域常用于解决此类逻辑推理问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

破解数独——回溯法

'''
1、确保不出现连续两次only=2的情况
2、只要做了only=2的试填,就压入Stack
   做only=1不压入栈!!
3、出现报错,立刻退栈,再错再退,直到退回第一次only=2的尝试

疑问:1、退档是否要退a b A B ans的所有档?
退档的具体操作:
先出栈,上一次only=2的尝试,将另外一个数字填入即可(即转化成only=1的情况),别的都不用管
'''
from copy import deepcopy
from time import clock
start = clock()
#f(x)函数的作用是,输入一组数据,返回1-9间的补集
def f(x):
    ans = []
    for i in range(1,10):
        if i not in x:
            ans.append(i)
    return ans

#用于检测是否出现数字重复,即报错
def check(x,b,C):
    flag = False
    for i in range(1,10):
        for j in x:
            if j.count(i)>1:
                flag = True
                break
    for i in range(1,10):
        for j in b:
            if j.count(i)>1:
                flag = True
                break
    for i in range(1,10):
        for m in C:
            for n in m:
                if n.count(i)>1:
                    flag = True
                    break
    if flag==True:
        return "Error"
    else:
        return "True"
        
stack_x = []
stack_a = []
stack_b = []
stack_A = []
stack_B = []
stack_ans = []
stack_M = [] #表示每次做only=2的尝试是,ans的行列
stack_N = []

#数独是9*9的二维数组a,输入空的用0表示
#先尝试获取输入,x表示最原始的数独
x = [[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],]

#输入数独
num = 0
while num!=9:
    line = raw_input()
    k=-1
    for i in line:
        k+=1
        x[num][k]=int(i)
    num += 1 
    
change = False #change指针用于指示是否将ans插入'x'

while True:
    #import copy模块的deepcopy复制数组
    #a数组共有9行,表示每行已经存在的数字0
    a = deepcopy(x)
    #去掉所有的0再次存入a
    for i in a:
        while True:
            if 0 in i:
                i.remove(0)
            else:
                break
            
    #b数组是输入的置换矩阵,即行列交换
    b = [[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],]
    for i in range(9):
        for j in range(9):
            b[i][j]=x[j][i]
    #去掉所有0再次存入b
    for i in b:
        while True:
            if 0 in i:
                i.remove(0)
            else:
                break
    
    #A、B两个数组表示a、b的补集,再同C三者求交集便表示可能填的数字
    A = [None,None,None,None,None,None,None,None,None]        
    for i in range(9):
        A[i]=f(a[i])
    
    B = [None,None,None,None,None,None,None,None,None]
    for i in range(9):
        B[i]=f(b[i])
        
    #尝试解决宫内重复的问题
    #c数组是3*3表示每个宫内的数字,去0再存入c
    c = [[[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None]],[[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None]],[[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None]]]
    temp = []
    for i in range(3):
        for j in range(3):
            temp.append(x[i][j])
    for i in range(9):
        c[0][0][i]=temp[i]
    
    temp = []
    for i in range(3):
        for j in range(3,6):
            temp.append(x[i][j])
    for i in range(9):
        c[0][1][i]=temp[i]
        
    temp = []
    for i in range(3):
        for j in range(6,9):
            temp.append(x[i][j])
    for i in range(9):
        c[0][2][i]=temp[i]
    
    temp = []
    for i in range(3,6):
        for j in range(3):
            temp.append(x[i][j])
    for i in range(9):
        c[1][0][i]=temp[i]
    
    temp = []
    for i in range(3,6):
        for j in range(3,6):
            temp.append(x[i][j])
    for i in range(9):
        c[1][1][i]=temp[i]
    
    temp = []
    for i in range(3,6):
        for j in range(6,9):
            temp.append(x[i][j])
    for i in range(9):
        c[1][2][i]=temp[i]
    
    temp = []
    for i in range(6,9):
        for j in range(3):
            temp.append(x[i][j])
    for i in range(9):
        c[2][0][i]=temp[i]
    
    temp = []
    for i in range(6,9):
        for j in range(3,6):
            temp.append(x[i][j])
    for i in range(9):
        c[2][1][i]=temp[i]
    
    temp = []
    for i in range(6,9):
        for j in range(6,9):
            temp.append(x[i][j])
    for i in range(9):
        c[2][2][i]=temp[i]
    
    #去掉所有的0重新存入数组c
    for i in c:
        for j in i:
            while True:
                if 0 in j:
                    j.remove(0)
                else:
                    break
    C = deepcopy(c)
    m=-1            
    for i in c:
        m+=1
        n=-1
        for j in i:
            n+=1
            c[m][n]=f(j)
    
    #求交集方法,list( set(a) & set(b) & set(c) )
    #ans代表每个空可以输入的数字
    ans = [[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None],[None,None,None,None,None,None,None,None,None]]
    for i in range(9):
        for j in range(9):
            if x[i][j]==0:
                ans[i][j]=list( set(A[i]) & set(B[j]) & set(c[i/3][j/3]))
            else:
                ans[i][j]=[]
    '''change指针为True,说明此处ans会导致连续两次的only=2,所以加'x'
    '''
    if change==True:
        print 'm,n:',stack_M[-1],stack_N[-1]
        print 'x:',x[stack_M[-1]][stack_N[-1]]
        print 'sdsadasdasdasd:',ans[stack_M[-1]][stack_N[-1]]
        if len(ans[stack_M[-1]][stack_N[-1]])!=0:
            ans[stack_M[-1]][stack_N[-1]].append('x')
        change=False
    
                
    '''还要优先检测是否已经填了重复的数字,这是用ans无法检测出来的'''
    if check(x,b,C)=="Error":
        print "Error exists"
        #退到上一个二选一的Stack档,并且换数字,交换位置即可
        a = stack_a.pop();b = stack_b.pop();
        A = stack_A.pop();B = stack_B.pop();
        M = stack_M.pop();N = stack_N.pop();
        ans = stack_ans.pop();x = stack_x.pop();
        ans[M][N].pop()
        print "after [Error] back stack x:"
        for i in x:
            print i
            
    '''一定要优先检测ans是否报错,一旦报错立刻退档'''
    #如果ans中该填空为位置有空集,说明暴力填词出错,退档即可
    m = -1
    for i in ans:
        flag1 = False #flag1检测是否出现error,一旦出现立刻退档并且跳出for循环
        m += 1
        n = -1
        for j in i:
            n += 1
            if x[m][n]==0 and ans[m][n]==[]:#在应该填空的位置ans为空,就报错
                flag1 = True
                print "line "+str(m),"row "+str(n)+"   Error exists!"
                #报错后立刻退档,退回上一个二选一
                a = stack_a.pop();b = stack_b.pop();
                A = stack_A.pop();B = stack_B.pop();
                M = stack_M.pop();N = stack_N.pop();
                ans = stack_ans.pop();x = stack_x.pop();
                #退回上一个only=2的情形,就将其自动变成only=1的ans空
                ans[M][N].pop()#因为试填默认试第二个,所以直接pop即可
                break
        if flag1==True:
            break

    #only表示只能填唯一数的空数
    #若only=0,则找到only=2的进行试填,直至再次出现only=1的情况
    only = False
    for i in range(9):
        for j in range(9):
            if len(ans[i][j])==1:
                print "There exists only one condition!"
                print "only a["+str(i)+"]["+str(j)+"]="+str(ans[i][j][0])
                only = True
                print ans[i][j],"!!!!!!!!!"
                print x[i][j]
                x[i][j]= ans[i][j][0]
                
    '''
    重点,做暴力试填的点,要做全局变量标记,因为可能会退档
    !!优化:优先选择填完Only=2后能产生Only=1的来优先Try,这就需要逐一去试,若不能产生Only=1就退档,再进入for寻找下一个Only=2的
    '''
    only2 = False
    if only == False:
        print "No only=1 exists@@@"
        flag2 = False
        m = -1
        for i in ans:
            m += 1
            n = -1
            for j in i:
                n += 1
                if len(j)==2:
                    only2=True
                    stack_a.append(deepcopy(a));stack_b.append(deepcopy(b));stack_A.append(deepcopy(A));stack_B.append(deepcopy(B));stack_M.append(deepcopy(m));stack_N.append(deepcopy(n));stack_ans.append(deepcopy(ans));stack_x.append(deepcopy(x))
                    
                    x[m][n]=ans[m][n][1] 
                    if check(x,b,C)=="Error":
                        x[m][n]=0
                        
                    #默认试填第二个数字(反正蒙中概率是50%)
                    flag2 = True #flag2表示是否有only=2的空,有的话立刻试填,并且所有数据入栈以作备份
                    break
            if flag2==True:
                break
    
    
    
    
    #Check检测是否还有0未填 
    Check = []
    for i in x:
        for j in i:
            Check.append(j)
    if 0 not in Check:#所有空已经填完,则跳出程序
        break
  
#如果能跳出while,那么一定产生了答案(但未必是正确的答案)  
if check(x,b,C)=="True":
    print "answer:" 
    for i in x:
        print i
else:
    print "Sorry I cannot @-@"
    
end = clock()
print int(start)
print int(end)
print end-start


数独样例

简单测试
600090020
972500803
035407001
000300059
050000010
320008400
500704190
701002584
040010007


大师级别
302700009
008000045
004001300
000059000
090030060
000260000
001400200
260000100
400002503

专家级
000000020
081006000
000000430
006001098
000000000
007000063
003569207
579200000
000007000


世界最难数独题 by芬兰数学家
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值