数独游戏的代码解题思路和过程

 

23.10.1假期无聊,玩了几个数独游戏,发现难度系数越高,解题速度越慢,最后cpu发烫了也没有解出最难的那一关,临近收假,我们考虑用代码来完成这一烧脑的过程,并总结下玩数独的解题思路。

一、数独的规则和解题思路

  1. 数独上的每一个格子,以其为中心的横向和竖向为1-9的不重复数字组成,并在每一个9方格中也是此规律。

  2. 对于简单的数独,通常利用排除法,每次会确定一到多个符合要求的唯一解的格子,对于复杂的高难度的数独,通常需要从候选的排列组合中先假设一个确定值,在后续的逻辑推算中,如果假设值满足要求则表明假设正确,不满足则排除此假设值。

  3. 然而对于高阶的数独,往往需要假定了很多的值,到推算时发现出现矛盾时,你也不知道哪一步假定值错误,因为这样的排列组合太多了,导致你的逻辑推算错误的假定值不一定为你假定的最后一个值,他可能出现在你假定中的任何一个值中。

二、初级数独的代码求解

1.给数独的每一个格子占位,并输入已知的、给出的数字,未填入的格子数值为0(自定义占位符)

import random
def orgin():
  global num2
  global input1
  num2 = []
  for a in range(1,10):
    num1 = []
    for b in range(1,10):
      num1.append(0)
    num2.append(num1)
  input1 = [(1,2,3),(1,3,7),(1,4,1),(1,8,6),(2,3,4),(2,5,5),(2,8,3),(3,7,4),(4,1,9),(4,2,8),(4,4,3),(5,1,6),(5,4,7),(5,6,2),(5,9,9),(6,6,6),(6,8,5),(6,9,2),(7,3,1),(8,2,9),(8,5,3),(8,7,7),(9,2,7),(9,6,9),(9,8,8),(9,7,5)]
  for a in input1:
    num2[a[0]-1][a[1]-1]=a[2]
​
orgin()

其中input1变量中储存的已知格子的数字,如(1,2,3)表示第一行第二列的数字为3.运行orgin()后我们将得到如下排列的数独

[0, 3, 7, 1, 0, 0, 0, 6, 0]
[0, 0, 4, 0, 5, 0, 0, 3, 0]
[0, 0, 0, 0, 0, 0, 4, 0, 0]
[9, 8, 0, 3, 0, 0, 0, 0, 0]
[6, 0, 0, 7, 0, 2, 0, 0, 9]
[0, 0, 0, 0, 0, 6, 0, 5, 2]
[0, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 9, 0, 0, 3, 0, 7, 0, 0]
[0, 7, 0, 0, 0, 9, 5, 8, 0]

2.满足上述数独规则的情况下,获取每个格子可能的数字,如果出现矛盾值,则返回num22 = []

def combine():
  global num22
  global com2
  num22 = num2
  com2 = []
  for a in [2,5,8]:
    com4 = []
    for b in [2,5,8]:
      com3 = []
      for c in [-1,0,1]:
        for d in [-1,0,1]:
          com3.append((num2[a+c-1][b+d-1]))
      com4.append(com3)
    com2.append(com4)
  for x in range(1,10):
    for y in range(1,10):
      if num22[x-1][y-1] == 0:
        if (x <=3) & (x >= 1) & (y <=3) & (y >= 1):
          jiu = com2[0][0]
        elif (x <=3) & (x >= 1) & (y <=6) & (y >= 4):
          jiu = com2[0][1]
        elif (x <=3) & (x >= 1) & (y <=9) & (y >= 7):
          jiu = com2[0][2]
        elif (x <=6) & (x >= 4) & (y <=3) & (y >= 1):
          jiu = com2[1][0]
        elif (x <=6) & (x >= 4) & (y <=6) & (y >= 4):
          jiu = com2[1][1]
        elif (x <=6) & (x >= 4) & (y <=9) & (y >= 7):
          jiu = com2[1][2]
        elif (x <=9) & (x >= 7) & (y <=3) & (y >= 1):
          jiu = com2[2][0]
        elif (x <=9) & (x >= 7) & (y <=6) & (y >= 4):
          jiu = com2[2][1]
        elif (x <=9) & (x >= 7) & (y <=9) & (y >= 7):
          jiu = com2[2][2]
        num3 = [m[y-1] for m in num2]
        num4 = []
        for z in range(1,10):
          if (z not in (num22[x-1]+num3)) & (z not in jiu):
            num4.append(z)
        if num4 == []:
          num22 = []
          return num22
          break
        else:
          num22[x-1][y-1]=num4
  return num22
​

3.变量com1记录拥有唯一数字的每个格子的位置和值,其中x,y表示位置坐标,x表示行,y表示列,num22[x-1][y-1]表示该位置坐标的值。

com1 = []
def xunh():
  global com1
  if num22 != []:
    for x in range(1,10):
      for y in range(1,10):
        if (type(num22[x-1][y-1]) != int):
          if (len(num22[x-1][y-1]) == 1):
            com1.append((x,y,num22[x-1][y-1]))
    return com1

4.运行上述函数后,将获得的唯一值的格子填充进数独中,并利用all()函数对新生成的数独进行更新,并记录新的唯一值的格子,并结果一并更新的com1变量中,直至没有出现含有唯一值的格子。

orgin()
combine()
xunh()
​
​
def all():
  global i
  global num2
  global num22
  i = 0
  while i < 81:
    i += 1
    orgin()
    for a in com1:
      num2[a[0]-1][a[1]-1]= a[2][0]
    combine()
    xunh()
​
all()

5.利用com1记录的唯一值的格子填充原始的数独,即可获得初级数独的答案

orgin()
for a in com1:
  num2[a[0]-1][a[1]-1]= a[2][0]
​

答案如下:

[0, 3, 7, 1, 0, 0, 0, 6, 0]
[0, 0, 4, 0, 5, 0, 0, 3, 0]
[0, 0, 9, 0, 0, 0, 4, 0, 0]
[9, 8, 2, 3, 0, 0, 0, 0, 0]
[6, 0, 5, 7, 0, 2, 0, 0, 9]
[0, 0, 3, 0, 0, 6, 0, 5, 2]
[0, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 9, 8, 0, 3, 0, 7, 0, 0]
[0, 7, 6, 0, 0, 9, 5, 8, 0]

com1变量为[(6, 3, [3]), (5, 3, [5]), (4, 3, [2]), (9, 3, [6]), (8, 3, [8]), (3, 3, [9])],如(6,3,[3])表示第六行第三列的格子拥有唯一值3;现在只找到6个含有唯一值的格子,并没有求出所有的值。这是因为目前展示的是等级为非常困难的数独,如果是中低等级的数独是可以全部求出答案的,有兴趣的可以尝试一下。

三、  高级数独解法

1.在初级数独的解法基础上继续;我们将含有两个可能值的格子找出,并利用mm变量储存初级算中单一值的格子。

mm = len(com1)
com5 = []
for x in range(1,10):
      for y in range(1,10):
        if (type(num22[x-1][y-1]) != int):
          if (len(num22[x-1][y-1]) == 2):
            com5.append((x,y,num22[x-1][y-1]))
​

com5变量为:[(4, 3, [6, 8]), (4, 5, [2, 4]), (4, 7, [4, 8]), (5, 2, [1, 4]), (5, 5, [2, 4]), (6, 1, [2, 5]), (6, 3, [1, 6]), (6, 7, [4, 7]), (7, 1, [5, 8]), (7, 2, [5, 7]), (7, 8, [3, 8]), (7, 9, [3, 7]), (9, 1, [8, 9]), (9, 3, [1, 8]), (9, 8, [2, 8])],如(4,3,[6,3])表示第六行第三列的格子可能为6也可能为3。

2.将全部的含有两个可能值的格子进行随机抽样,并将抽样的结果加入com1中,抽样的结果在com1中满足横坐标或者纵坐标相同时不同格子的值必须不同,如果相同的则重新抽样,直至满足要求。然后运行all()函数,如果num22 = [],或者新生成com1中不满足逻辑,则表明生成的新的com1对于后续单一值格子的逻辑推算有矛盾的地方,则com1的值回到抽样前,进行重新抽样,直至满足要求。

num22 = []
while (aa != 0) or num22 == []:
    aa = 1
    com1 = com1[0:mm]
    while (aa != 0):
      com1 = com1[0:mm]
      aa = 0
      for e in com5:
        com1.append((e[0],e[1],[random.choice(e[2])]))
      for a in com1:
        for b in com1:
          if (a[0] == b[0]):
            if (a != b):
              if a[2] == b[2]:
                aa += 1
          if (a[1] == b[1]):
            if (a != b):
              if a[2] == b[2]:
                aa += 1
    all()
    for a in com1:
      for b in com1:
        if (a[0] == b[0]):
          if (a != b):
            if a[2] == b[2]:
              aa += 1
        if (a[1] == b[1]):
          if (a != b):
            if a[2] == b[2]:
              aa += 1
​

3.运行orgin(),将num2归位到初始的数独,利用com1储存的单一值格子进行填充,并获得求出全解的数独

orgin()
for a in com1:
    num2[a[0]-1][a[1]-1]= a[2][0]

4.结果如下:

[8, 3, 7, 1, 9, 4, 2, 6, 5]
[1, 6, 4, 2, 5, 7, 9, 3, 8]
[2, 5, 9, 8, 6, 3, 4, 1, 7]
[9, 8, 2, 3, 4, 5, 1, 7, 6]
[6, 1, 5, 7, 8, 2, 3, 4, 9]
[7, 4, 3, 9, 1, 6, 8, 5, 2]
[4, 2, 1, 5, 7, 8, 6, 9, 3]
[5, 9, 8, 6, 3, 1, 7, 2, 4]
[3, 7, 6, 4, 2, 9, 5, 8, 1]

大家可以看看,结果是否符合要求。

四、代码数独游戏算法总结

  1. 矢量性:需要给每个格子占位,因为每个格子值与横行和竖行以及9宫格的值相关(1-9不重复),所以体现出位置属性、方向属性,也叫矢量

  2. 降维:无论是初级数独还是高级数独,最后都需要转化为定向和定量,也就是说不管哪个格子,必须要确定符合要求的唯一值,也就是上述我们用random.choice()函数进行取值的原因

  3. 发展的眼光:计算机语言中变量是可以被赋值,因此做好复位和多次判断是很重要的,因为新变量可以悄无声息的覆盖旧变量的,这很符合马克思主义思想:用发展的眼光看待事物的发展;在这里就是用更新的思路看新的变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值