数独问题流程图_算法实践——数独的基本解法

本文详细介绍了数独问题的解决流程,包括数独的基本规则、唯一数单元格的概念和处理方法,以及如何利用摒弃法求解数独。算法通过使用一维数组表示数独状态,并利用位运算进行单元格状态的更新。遇到无唯一数单元格时,通过栈保存当前状态并尝试所有可能的填数方案,直到找到唯一解。算法还包括了状态的缓存、求解过程的记录以及主求解函数的实现。
摘要由CSDN通过智能技术生成

数独(Sudoku)是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫内的数字均含1-9,不重复。 每一道合格的数独谜题都有且仅有唯一答案,推理方法也以此为基础,任何无解或多解的题目都是不合格的。

如下图所示,就是一个数独的题目

关于数独的详细介绍,参看“百度百科——数独”

数独的基本解法就是利用规则的摒弃法

一些定义

每一行称为数独的行,每一列称为数独的列,每一个小九宫格称为数独的宫。数独的基本规则就是每一行、每一列、每一宫中,1-9这9个数字都只出现一次。

用(行,列)表示上图的单元格,例如(1,1)表示第一行第一列的单元格,(2,4)表示第二行第四列的单元格

如上图,每个空白单元格中能填的数字都是有限制的。

例如:(1,1)就只能填7和8;而(6,4),只能填8;

那些只能填一个数字的空白单元格,我们称之为唯一数单元格,上图中(6,4)就是唯一数单元格

解题的顺序,就是从唯一数单元格开始,由于唯一数单元格只能填一个数,故先在这个单元格里填数。在这个单元格里填数,由于规则的定义,那么这个单元格所在的行、所在的列、所在的宫的其他单元格就不能再填这个数了。这些单元格能填的数的可能性就少了。有可能会产生新的唯一数单元格。

在相当的一些的数独题目中,从唯一数单元格开始填数,不停的在唯一数单元格填数就可以把数独解出来。

如果在解题的过程中,发现某些空白单元格没有数字能填这样的单元格称之为无解单元格,那就说明:要么这个数独没有解;要么之前的解题过程有问题,需要返回检查之前的解题过程查看。

但是还有不少的数独的题目,在解题的过程中,在还有空白单元格的情况下,却找不到唯一数单元格,也就是意味着每个空白单元格中能填的数字至少有2个。我们称之为无唯一数单元格的状况

这个时候怎么办?我们找到其中一个可能数最少的空白单元格(这个没有定论,可以是可能数最少的空白单元格;也可以是第一个空白单元格;也可以是可能数最多的空白单元格,选哪个空白单元格对后面的解题是否有影响,没有证明过,不好妄下定论。凭感觉选可能数最少的空白单元格是最好的选择),由于能填的数字不止一个,先把当前的状态保存起来,再在能选的数字中选择一个数字填写(从小到大选择),然后继续求解下去。如果能解出最后的结果,说明当前的选择是正确的;如果后面的求解过程有问题,说明当前的数字的选择有问题,那么再挑选另一个数填写,继续求解。如果,所有的选择都求不出最后的结果,还是说明:要么这个数独没有解;要么之前的解题过程有问题,需要返回检查之前的解题过程查看。如此反复,直到求出最终的答案。

会有种极端的情况(可能性不大)。那就是在当前的空白单元格的所有可能的数字都选择了一遍,都没有解。而之前又没有出现无唯一数单元格的状况。那就说明这个数独根本就没有解

下图是数独求解的流程图

下面谈谈该算法的具体实现

1、数独状态的表示

用计算机来求解数独。基本的一点就是如何表示数独的状态。

用整形一维数组来表示数独的状态

用Num(80)表示数独的状态(数组的下标从0开始),数独是一个二维表格,而数组是一维数组。那么就存在一维和二维之间的转换

一维数组的下标Index(小标从0开始)和二维下标X、Y(下标从0开始)之间的转换公式

一维到二维的转换

X=Int(Index/9)

Y=Index mod 9

二维到一维的转换

Index=X*9+Y

数组中的每个整数表示数独对应的单元格的状态

正数表示空白单元格能填的数的组合,用二进制表示。用位来表示该单元格是否能填相应的数字,1表示能填,0表示不能填。

如文章开始的数独的单元格(1,1)可能填7和8,则第7位和第8位上是1(位数是从后往前数),其余位都是0,用整数表示就是Num(0)=0110000002=192

每在单元格中填一个数字,则把相应的行、列、宫中其余的单元格把该数字去掉。

我们可以充分利用位运算来简化去数字的过程。如:要把单元格去掉7这个数字的可能。首先7对应的二进制位0010000002,取其反数得到1101111112,再和目标单元格的数值进行AND的位运算,来实现去除该单元格7这个数字的可能性(由于位运算的便捷,不需要考虑该单元格是否原本包含7这个数字的可能性)。

如:(1,1)=0110000002 AND 1101111112=0100000002,去除7这个可能性,只剩8这个可能性了,也就是成为唯一数单元格

再比如:(1,9)=0100000102 AND 1101111112=0100000102,原本单元格就没有7这个可能性,执行位运算后,还是原来的可能性,没有发生变化。

负数表示该单元格已经确定的数,例如:(1,2)=-6,表示该单元格已近填了数字6

0表示该单元格既没有填确定的数字,也没有可填数的可能性。也就是上文说的无解单元格

为了算法中计算的方便,事先把这些二进制数都缓存起来,用一个一维的数组表示

用数组V来表示各个位对应的数字

V(0)=0000000012=1

V(1)=0000000102=2

V(2)=0000001002=4

V(3)=0000010002=8

V(4)=0000100002=16

V(5)=0001000002=32

V(6)=0010000002=64

V(7)=0100000002=128

V(8)=1000000002=256

V(9)=1111111112=511

数字7对应的二进制数为V(6)=0010000002=64,7的反数为V(9)-V(6)=1101111112=447

每个单元格初始的值都是V(9)=1111111112=511

2、如何获得一个单元格的可填数的个数

由于是用二进制来表示单元格的状态,那么可填数的个数就是该数字中1的个数。我们之前有一个很方便的方法快速计算一个数中1的个数,参看算法的强大——快速计算一个正二进制整数中包含多少个1。

3、状态的缓存

依据之前的说法,在碰到无唯一数单元格的情况时,要把当前的状态缓存起来。考虑到实际情况,从算法的角度上来说,用栈(先进后出)这个数据结构来实现比较合适。可以自己写一个栈的实现。但是,目前很多的编程语言都实现了基本的数据结构,提供了基本的数据结构的类和方法供我们调用。

以Visual Studio为例,它有Stack这个类,实现了栈的基本操作。有两个栈的方法:Push(压栈)——把数据写到栈里面;Pop(出栈)——把数据从栈里提出来,并删除栈中的数据。

4、代码说明

基本的变量

Private _Num(80) As Integer

Private _V(9) As Integer

Private _S As System.Text.StringBuilder

Private _HasString As Boolean

_Num数组表示数独的状态;_V数组是辅助数组,缓存常用的二进制数

_S是一个文本对象,保存数独求解的过程;_HasString是个开关变量,表示是否记录求解过程;这两个变量是辅助变量,仅仅起到记录的作用。

类的初始化

Public Sub New(Optional ByVal HasString As Boolean = True)

Dim I As Integer

_V(0) = 1

For I = 1 To 8

_V(I) = _V(I - 1) * 2

Next

_V(9) = 511

For I = 0 To 80

_Num(I) = _V(9)

Next

_S = New System.Text.StringBuilder

_HasString = HasString

End Sub

代码的前半段生成V这个数组,_V(9)=511。后半段,初始化数独数组。由于是空白数独数组,故每个单元格的值都是_V(9)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值