title: 高效算法求解数独

date: 2019-12-26 17:55:16

tags: 数据结构与算法

categories: 数据结构与算法

背景

??之前上python课的时候,有一次实验是求解数独,要求时间复杂度要低;为此老师讲解了一个高效的数独算法,我觉得算法挺有意思的,写篇博客记录一下。

描述

首先需要知晓数独的两个规则:

若某个位置的值已经确定,那么,和这个位置在同一行,同一列,同一个3×3的格子,都不能填写这个值,比如,九宫格(1,1)位置的值为2,那么,第一行,第一列,以及第一个3×3的格子里,都不能在填2了;

若某一行,或者某一列,或者某一个3×3里面,只有一个位置可能填1(假如是1),那么1一定是填写在这个位置,因为没有其他位置可以填它了;、

求解步骤

创建一个三维数组,假设就叫“可能值数组”,记录数独9×9的81个位置中,每个位置可能填写的值,初始情况下,每个位置的可能值都是1到9,表示每个位置都可能填写1-9中任何一个数字;

遍历数独的每一个位置,若某个位置已经有值,则将这个位置的可能值更新为这个值,比如,九宫格上,(1,1)的值已经确定是2了,那就将三维数组中(1,1)位置的可能值从[1-9]更新为[2],直到所有的位置更新完毕;

使用上述规则1进行剪枝:

(1):从第一个位置开始遍历九宫格,若当前遍历到的位置(i,j),它的值已经知晓,那么就更新可能值数组,将第i行,第j列,以及其对应的3×3(【i/3×3 , j/3×3】就是这个3×3的第一个点)的所有位置,它们的可能值都要去除(i,j)位置的值;

(2):若某个位置在经过上一步的剪枝后,可能值只剩下一个了,那这个位置的值就确定了,比如说,位置(1,1)的初始可能值是1到9,经过上面的一步步去除,只剩下一个3了,那这个(1,1)位置填写的值必定就是3了。此时我们可以再次使用规则1,即第一行,第一列,以及其对应的3×3中,所有的格子的可能值不能有3;

(3):依次遍历每一个位置,使用上面的规则1,直到最后一格子,第一次剪枝便完成了;

使用上面的规则2进行剪枝:

(1):统计每一行,每一列,以及每一个3×3中,每个数出现的次数,比如统计第一行每个格子的可能值,看1-9各出现几次,若某个可能值只出现一次,那出现这个值的位置,就是填写这个值,比如说,在第一行,3这个数字,只有(1,1)这个位置可能填写,那(1,1)就是填3,因为其他位置的可能值当中都不包含3,也就是都不能填写3;

(2):根据上一步确定了某个位置的值后,那我们此时又可以使用规则1了,比如,上一步确定了(1,1)是填写3,那么第一行,第一列,以及第一个3×3中其余的格子, 都不能在写3了,我们从可能值数组中,将这些位置的可能,值删除3这个数;

(3):而此时,又可能出现上面的第3步中的(3)的情况;

规则2剪枝完毕后,数独还没有解决完毕,那我们只能通过枚举每一个位置的值,来一一尝试,直到找到最后的解决办法:

(1):我们在最开始创建了一个三维数组,存储每一个位置的可能值,初始情况下,每个位置的可能值都是1-9,但是经过上面两个规则的剪枝后,已经去除了很多;

(2):此时我们使用DFS深度优先搜索,尝试为每一个位置填值。经过上面的剪枝,每个位置的可能值的数量应该不一样了,而为了减少DFS搜索的次数,我们应该从可能值最少的位置开始搜索;

(3):遍历9宫格,找出还未填写值,且可能值最少的那个位置(可能有多个,找出第一个),尝试将他的第一个可能值填写在这个位置,然后再次调用规则1和规则2进行剪枝,剪枝完毕后,判断当前的九宫格中,是否有不和规则的地址,比如同一行出现两个一样的数。若没有不合法的地方,则再次进行一次(3),若有,表示这个位置不能填这个值,则从这个位置的可能值中再选择另外一个;

(4):一直使用步骤(3),直到所有的位置都确定,则表示成功解出数独,若有某个位置,它的任何一个可能值填上去,都不能得到最终结果,那数独就是无解的;

经过上面这些步骤,就能快速的解出数独,因为主要通过规则1,2进行剪枝,大大减少了枚举的次数,提升了效率;

所需计算

已知位置(i,j),则这个位置所在的3*3,其第一个点是(i/3×3 , j/3×3),i/3×3表示先作除法,去除了小数部分,再乘3,就是3的倍数了;

已知位置(i,j),如何计算这个位置属于第几个3×3,那就是(i/3×3 + j/3),每个3*3都占3行,且3列,i/3得到这个位置在第几个3行,j/3得到这个位置在第几个3列,每三行有三个3×3,所以i/3×3 + j/3就可以得到这个位置在第几个3×3;

代码

因为是为了完成python实验,所以代码是用python写的:

# 此类用来表示搜索时,需要搜索的一个位置

# x,y为此位置的坐标,size为此位置的可能值的数量

class Node:

def __init__(self, x, y, size):

self.x = x

self.y = y

self.size = size

# 读取方式2,读取top95

def read_way2():

# 从文件中读取初始数独

value = [[0] * 10 for i in range(10)]

# 读取文件2,3

s = infile.readline();

if s == '':

return

for i in range(9):

for j in range(9):

value[i][j] = int(s[i * 9 + j])

return value

# 初始化函数

def init():

value = read_way2() # 读取top95

# 初始化possibleValue,若当前位置有值,则其可能值就是这个值本身

# 若没有值,则初始的可能值就是1-9

possibleValue = [[[] for j in range(10)] for i in range(10)]

for i in range(9):

for j in range(9):

if value[i][j] != 0:

possibleValue[i][j] = [value[i][j]]

else:

possibleValue[i][j] = [1, 2, 3, 4, 5, 6, 7, 8, 9]

return possibleValue

#####################################################################################################################

# 根据规则1进行剪枝

# 遍历所有的位置,找到已经确定的位置进行剪枝

def pruningByRule1(possibleValue):

for i in range(9):

for j in range(9):

if len(possibleValue[i][j]) == 1:

removeValueByRule1(i, j, possibleValue) # 以当前位置为起点,移除重复的可能值

# 在规则1剪枝中,将同一区域中,已经确定的数移除

# 以(i,j)位置为起点,移除重复的可能值

def removeValueByRule1(i, j, possibleValue):

# 与当前值在同一行或同一列的位置,可能值减去当前位置的值

for k in range(9):

# 从第i行中的可能值列表中,移除当前值

confirmOneValueInRule1(i, k, possibleValue[i][j][0], possibleValue)

# 从第i列中的可能值列表中,移除当前值

confirmOneValueInRule1(k, j, possibleValue[i][j][0], possibleValue)

# 与当前值在同3*3的位置,可能值减去当前位置的值

for k in range(int(i / 3) * 3, int(i / 3) * 3 + 3):

for l in range(int(j / 3) * 3, int(j / 3)* 3 + 3):

confirmOneValueInRule1(k, l, possibleValue[i][j][0], possibleValue)

# 移除某个位置的可能值,并在移除后判断能否得到确定值

def confirmOneValueInRule1(i, j, num, possibleValue):

if len(possibleValue[i][j]) == 1:

return

# 从当前位置的可能值中,移除已经确定的数

if num in possibleValue[i][j]:

possibleValue[i][j].remove(num)

# 判断移除后,当前位置能否确定

if len(possibleValue[i][j]) == 1:

# 若当前位置确定,则以当前位置为基准进行移除操作

removeValueByRule1(i, j, possibleValue)

###########################################################################################

# 根据规则2剪枝,判断同一个区域每个值可能出现的次数

# 若某个值可能出现的位置只有一个,表示这个值就在此位置

def pruningByRule2(possibleValue):

# 统计第i行,数字j可能值出现了几次

countX = [[0] * 10 for i in range(12)]

# 统计第i列,数字j可能值出现了几次

countY = [[0] * 10 for i in range(12)]

# 统计第i个3*3,数字j可能值出现了几次

countZ = [[0] * 10 for i in range(12)]

# 统计各个区域可能值出现的次数

for i in range(9):

for j in range(9):

for num in possibleValue[i][j]:

countX[i][num] += 1

countY[j][num] += 1

countZ[i // 3 * 3 + j // 3][num] += 1

# 判断哪些数字只出现了一次, 若只出现了一次的数字

# 表示这个数字就是那个位置的答案

for i in range(9):

for j in range(1,10):

# 若第i行数字j只出现了一次

if countX[i][j] == 1:

for k in range(9): # 遍历第i行的每一列,判断这个唯一值出现在哪

confirmValueInRule2(i, k, j, possibleValue)

# 若第i列数字j只出现了一次

if countY[i][j] == 1:

for k in range(9): # 遍历第i列的每一列,判断这个唯一值出现在哪

confirmValueInRule2(k, i, j, possibleValue)

# 若第i个3 * 3中,数字j的可能值只有一个

if countZ[i][j] == 1:

# 遍历第i个3*3的所有位置,判断这个唯一值出现在哪

for k in range(i//3*3, i//3*3+3):

for l in range(i%3*3, i%3*3+3):

confirmValueInRule2(k, l, j, possibleValue)

# 判断当前位置是否包含某个数,包含则为此位置的答案

def confirmValueInRule2(i, j, singleNum, possibleValue):

# 若当前位置已经确定值了, 直接返回

if len(possibleValue[i][j]) ==1:

return

# 若当前位置包含唯一可能值,则这个位置的确定值就是它

if singleNum in possibleValue[i][j]:

possibleValue[i][j] = [singleNum]

# 重新调用规则1

removeValueByRule1(i, j, possibleValue)

###########################################################################################

# 递归搜索

def searchForPruning(node, possibleValue):

# 若没有需要填值的点了,表示搜索结束,答案已出

if node is None:

return possibleValue

# 获取当前位置的x,y坐标

x = node[0]

y = node[1]

for num in possibleValue[x][y]:

# 复制一份当前状态

tempPossibleValue = copy.deepcopy(possibleValue)

# 更新数据

tempPossibleValue[x][y] = [num]

# 调用规则1,2

removeValueByRule1(x, y, tempPossibleValue)

pruningByRule2(tempPossibleValue)

# 调用规则1,2后,判断当前结果是否合法,若合法,则进行递归下一层

if judge_result(tempPossibleValue):

# 递归求解

tempPossibleValue = searchForPruning(get_lowest_node(tempPossibleValue), tempPossibleValue)

# 判断递归结果,若结果有返回值,则表示求解成功

if tempPossibleValue is not None:

return tempPossibleValue

# 获取当前可能值最小的位置

def get_lowest_node(possibleValue):

minn = 100

node = None

for i in range(9):

for j in range(9):

# 若当前位置没有确定值,并且可能值的数量更少,则更新记录,

if 1 < len(possibleValue[i][j]) < minn:

minn = len(possibleValue[i][j])

node = (i, j)

return node

# 判断某个位置是否可以放某个值

def judge_result(possibleValue):

# 标记某个数字是否出现

countX = [[False] * 10 for i in range(12)]

countY = [[False] * 10 for i in range(12)]

countZ = [[False] * 10 for i in range(12)]

# 统计各个区域可能值出现的次数

for i in range(9):

for j in range(9):

if len(possibleValue[i][j]) == 1:

# 若当前状态不合法,返回false

if countX[i][possibleValue[i][j][0]] or countY[j][possibleValue[i][j][0]] or countZ[i // 3 * 3 + j // 3][possibleValue[i][j][0]]:

return False

# 若合法,则标记已经确定的数字

countX[i][possibleValue[i][j][0]] = True

countY[j][possibleValue[i][j][0]] = True

countZ[i // 3 * 3 + j // 3][possibleValue[i][j][0]] = True

return True

# 判断某个位置是否可以放某个值

def judge_now_number(possibleValue, i, j, num):

# 判断num在这一行和这一列是否被使用

for k in range(9):

if len(possibleValue[i][k]) == 1 and possibleValue[i][k][0] == num:

return False

if len(possibleValue[k][j]) == 1 and possibleValue[k][j][0] == num:

return False

# 判断num在这个3*3是否被使用

for k in range(int(i / 3) * 3, int(i / 3) * 3 + 3):

for l in range(int(j / 3) * 3, int(j / 3) * 3 + 3):

if len(possibleValue[k][l]) == 1 and possibleValue[k][l][0] == num:

return False

return True

###########################################################################################

# 输出展示可能值列表

def display(possibleValue):

for i in range(9):

for j in range(9):

print(possibleValue[i][j], end="---")

print()

print()

###########################################################################################

# 主函数

def main():

start = time.time()

c = 0

# 主逻辑

while True:

# 调用初始化函数

possibleValue = init()

# 调用规则1剪枝

pruningByRule1(possibleValue)

# 调用规则2剪枝

pruningByRule2(possibleValue)

# display(possibleValue)

possibleValue = searchForPruning(get_lowest_node(possibleValue), possibleValue)

# 判断是否有解

if possibleValue is not None:

display(possibleValue)

else:

print("无解")

if not judge_result(possibleValue):

print("结果异常")

c += 1

if c >= 90:

break

end = time.time()

print(end - start)

# 读取本地存储文件

infile = open("D:/top95.txt")

main()

扩展

??数独求解的算法,上面这种并不是最快的,还有一种叫做舞蹈链(Dancing Links)的算法,效率更高,有兴趣的可以了解一下;

php 数独求解,高效算法求解数独(示例代码)相关推荐

  1. java回溯算法解决数独_js回溯算法解决数独问题

    直接上代码 代码里面注释很清晰 传说中的最难数组大概是在20ms左右解决 /** * 数独算法 */ class Sudoku { constructor({ display = false, sud ...

  2. 归并排序 java 迭代_经典排序算法之归并排序(示例代码)

    归并排序(英语:Merge sort,或mergesort),是创建在归并操作上的一种有效的排序算法,效率为 (大O符号).1945年由约翰·冯·诺伊曼首次提出.该算法是采用分治法(Divide an ...

  3. python数据结构与算法知识点_数据结构和算法基础知识点(示例代码)

    数据结构和算法基础知识点 链表 1.链表是一种由节点组成的线性数据集合,每个节点通过指针指向下一个节点.它是 一种由节点组成,并能用于表示序列的数据结构. 2.单链表:每个节点仅指向下一个节点,最后一 ...

  4. matlab运动控制算法教程,机器人学、机器视觉与控制:MATLAB算法基础pdf(示例代码)...

    下载地址:网盘下载 内容简介 本书是关于机器人学和机器视觉的实用参考书, 第一部分"基础知识"(第2章和第3章)介绍机器人及其操作对象的位置和姿态描述,以及机器人路径和运动的表示方 ...

  5. 五子棋算杀c语言,五子棋AI算法-算杀(示例代码)

    关于剪枝问题 前面讲到的通过Alpha-Beta剪枝和启发式搜索可以将4层搜索的平均时间降低到1秒以下.只有这两个优化方式其实目前最多可以做到6层搜索,就是把AI和玩家各向后推算三步. 6层搜索的棋力 ...

  6. 音频自动增益 与 静音检测 算法 附完整C代码

    前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...

  7. java mp3静音检测,音频自动增益 与 静音检测 算法 附完整C代码

    前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...

  8. java课程 数独 文库_一次数独生成及求解方案的剖析(Java实现)

    数独生成及求解方案剖析(Java实现) 关键词 数独9x9 数独生成 数独解题 序言 最近业务在巩固Java基础,编写了一个基于JavaFX的数独小游戏(随后放链接).写到核心部分发现平时玩的数独这个 ...

  9. python图像数独_Python图像识别+KNN求解数独的实现

    Python-opencv+KNN求解数独 最近一直在玩数独,突发奇想实现图像识别求解数独,输入到输出平均需要0.5s. 整体思路大概就是识别出图中数字生成list,然后求解. 输入输出demo 数独 ...

  10. 【C++】公元前五世纪,我国古代数学家张丘建在《算经》一书中提出了“百鸡问题”:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,问鸡翁、鸡母、鸡雏各几何?请设计一个“高效”的算法求解。

    题目分析 公元前五世纪,我国古代数学家张丘建在<算经>一书中提出了"百鸡问题":鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一.百钱买百鸡,问鸡翁.鸡母.鸡雏各几何?请设计一 ...

最新文章

  1. .ne中的控制器循环出来的数据如何显示在视图上_Web程序设计-ASP.NET MVC4数据库操作实例...
  2. 210328卡式水分开阶段总结
  3. php通过$_SERVER['HTTP_USER_AGENT']获取浏览器useAgent
  4. 计组-CPU的功能和基础结构
  5. python闯关_Day012
  6. Redis 分布式集群搭建2022版本+密码(linux环境)
  7. php 武汉海关对接_“双11”临近 海口海关全力备战跨境电商监管高峰
  8. 隐藏esp_汽车一键启车主必须知道的几个“隐藏”技巧
  9. 【TensorFlow-windows】(三) 多层感知器进行手写数字识别(mnist)
  10. java基于Springboot+vue的零食销售购物商城 elementui 前后端分离
  11. CDIO工程实践 无线充电智能循迹小车制作
  12. 校招行测笔试-言语理解与表达
  13. tensorflow配置默认工作路径
  14. 对话时人品牌咨询首席咨询官:有多少企业倒在了自己的品牌故事上?
  15. SQL中进行去重的方法
  16. 德国互联网现状,缺人,4万元每月的收入,可以考虑移民了
  17. ZOJ 2480 Simplest Task in Windows
  18. 不带ui的服务器系统,win不含ui的云服务器
  19. luogu P3647 [APIO2014] 连珠线
  20. 【超全面】机器学习中的超参优化方法总结

热门文章

  1. 【黑马程序员数据库】数据库基础大总结
  2. AndroidStudio:设计一个能在图片上涂鸦的程序
  3. 我的csdn账号开通啦~
  4. Tomact运行不起来,打开startup.bat 一闪而过
  5. Delphi编程(二)__Delphi安装
  6. 稚晖君软件硬件开发环境总结
  7. 蓝桥杯官网练习系统入门训练(二)
  8. 前端CSS基础——表单元素单选框的美化
  9. centos实现证书登录禁止密码登录
  10. vb.net操作excel文件