算法实践——数独的基本解法

简介:

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

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

0823dd54564e92588890f8419c82d158cdbf4efc

 

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

 

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

 

一些定义

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

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

 

 

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

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

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

 

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

 

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

 

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

 

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

 

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

 

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

 

下图是数独求解的流程图

image

 

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

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)

 

 

在给定的单元格里移除某个数字的可能性代码


     Private  Function RemoveNum( ByVal Row  As  IntegerByVal Col  As  IntegerByVal Num2  As  IntegerAs  Integer 
         Dim Index  As  Integer = Row * 9 + Col 
         If _Num(Index) > 0  Then _Num(Index) = _Num(Index)  And Num2 
         Return _Num(Index) 
     End  Function

3个参数,Row表示行,Col表示列(都是下标从0开始),Num2表示要去除的数的反码,以二进制表示。

例如:在(1,1)这个单元格去除7这个可能性,则调用RemoveNum(0,0,1101111112

返回值是该单元格的状态值,如果返回0,表示该单元就成了无解单元格,要后面的代码做适当的处理

 

 

在给定的单元格填某个数的代码


     Private  Function SetNumPri( ByVal Row  As  IntegerByVal Col  As  IntegerByVal Num  As  IntegerAs  Boolean 
         If (_V(Num)  And _Num(Row * 9 + Col)) = 0  Then  Return  False 
        _Num(Row * 9 + Col) = -(Num + 1) 
        Num = _V(9) - _V(Num) 

         Dim I  As  Integer, J  As  Integer 

         For I = 0  To 8 
             If RemoveNum(I, Col, Num) = 0  Then  Return  False 
             If RemoveNum(Row, I, Num) = 0  Then  Return  False 
         Next 

         Dim R1  As  Integer = Int(Row / 3) * 3 
         Dim C1  As  Integer = Int(Col / 3) * 3 

         For I = R1  To R1 + 2 
             For J = C1  To C1 + 2 
                 If RemoveNum(I, J, Num) = 0  Then  Return  False 
             Next 
         Next 

         Return  True 
     End  Function 

3个参数,Row表示行,Col表示列,Num表示要填充的数字(下标从0开始),这个方法是供类内部调用,从程序的角度来说,程序处理下标,从0开始比从1开始要来得简单。

例如:在(1,1)中填入数字7,则调用SetNumPri(0,0,6)

代码的第1行,先利用位运算判断当前单元格能否填制定的数字,不能填返回False

代码的第2行,设置当前单元格为指定数字,之前说了,用负数表示已填好的数字

代码的第3行,获得当前数字的反码,为后面去除该单元格所在的行、列、宫的其他单元格的该数字做准备

后面有两个循环,第一个循环去除行、列的其他单元格的该数字;第二个双循环去除宫的其他单元格的该数字。在调用RomoveNum方法时,若返回的是0,说明产生了无解单元格,那说明在这个单元格填该数字是不合理的,故返回False

当全部的代码都能顺利完成了,说明这个单元格填该数字是合理的,返回True

 

该方法的另一个重载形式


     Private  Function SetNumPri( ByVal Index  As  IntegerByVal Num2  As  IntegerAs  Boolean 
         Dim Row  As  Integer = Int(Index / 9) 
         Dim Col  As  Integer = Index  Mod 9 
         Dim I  As  Integer 
         For I = 0  To 8 
             If _V(I) = Num2  Then  Exit For 
         Next 
         Return SetNumPri(Row, Col, I) 
     End  Function

这也是一个供内部调用的方法,两个参数,Index是一维数组的下标;Num2是数字的二进制的形式。整个方法就是参数的转换,然后调用之前的方法

 

下面是两个供外面调用的方法


     Public  Function SetNum( ByVal Row  As  IntegerByVal Col  As  IntegerByVal Num  As  IntegerAs  Boolean 
         Return SetNumPri(Row - 1, Col - 1, Num - 1) 
     End  Function 

     Public  Function SetLine( ByVal Row  As  IntegerByVal  ParamArray Num()  As  IntegerAs  Boolean 
         If Num.Length = 0  Then  Return  True 

         Dim I  As  Integer 

         For I = 0  To IIf(Num.Length - 1 > 8, 8, Num.Length - 1) 
             If Num(I) > 0  AndAlso SetNumPri(Row - 1, I, Num(I) - 1) =  False  Then  Return  False 
         Next 

         Return  True 

     End  Function

第一个方法是公开给外部调用的填数的方法。对外来说,从直观性上来说,下标是从1开始比较合适,但是内部的方法从0开始比较好。

如在(1,1)填7,调用SetNum(1,1,7),这个方法转而调用SetNumPri(0,0,6)

这个方法一般用在初始化数独时候调用

 

第二个方法也是公开给外部的方法,一次填写一行数的方法,如果是空白单元格,则用0替代

如本文开始的数独,填写第一行代码就是SetLine(1,0,6,0,5,9,3,0,0,0)

 

 

几个辅助方法


     Private  Sub RestoreNum( ByVal L  As  List( Of  Integer)) 
         Dim I  As  Integer 
         For I = 0  To 80 
            _Num(I) = L.Item(I) 
         Next 

        AppendString( "Restore Matrix"
     End  Sub

恢复L中的数据到数独数组中,L是之前缓存的数据。AppendString这个方法是将数据记录到文本对象

 


     Private  Function Get1Count( ByVal Value  As  IntegerAs  Integer 
         Dim C  As  Integer = 0 
         Do  While Value > 0 
            Value = Value  And (Value - 1) 
            C += 1 
         Loop 
         Return C 
     End  Function

获得一个数中1的个数,也就是获得一个空白单元格的可填数的数目

例如:(1,1)=0110000002,Get1Count(0110000002)=2,说明(1,1)这个单元格能填2个数

 


     Private  Function GetIndexOfNum( ByVal Num  As  IntegerByVal Index  As  IntegerAs  Integer 
         Dim I  As  Integer, K  As  Integer = 0 
         For I = 0  To 8 
             If (_V(I)  And Num) <> 0  Then 
                K += 1 
                 If K = Index  Then  Return I + 1 
             End  If 
         Next 
         Return -1 
     End  Function

获得指定数Num(二进制形式)的第Index个的可填数

还是以上面的为例,(1,1)=0110000002

GetIndexOfNum(0110000002,1)=7,表示第1个可填数是7

GetIndexOfNum(0110000002,2)=8,表示第2个可填数是8

GetIndexOfNum(0110000002,3)=-1,表示没有第3个可填数

 

 

辅助记录函数

这些函数对求解算法没啥太大的帮助,仅仅是将求解的过程记录到文本中,以供日后研究参考


     Private  Function ReturnNumString( ByVal Num  As  IntegerAs  String 
         If Num < 0  Then  Return  "#" & (-Num) &  " " 
         Dim I  As  Integer, S  As  String =  "" 
         For I = 0  To 8 
             If (_V(I)  And Num) <> 0  Then S &= (I + 1) 
         Next 
         Return S.PadRight(10) 
     End  Function

返回一个数字的文本格式,如果是空白单元格,返回该单元格的所有可填数;如果是已填单元格,返回#+数字的字符串。返回的字符串经过对齐处理。

 

 

 

 


     Private  Function ReturnMatrix()  As  String 
         Dim I  As  Integer, J  As  Integer, S  As  String =  "" 
         For I = 0  To 8 
             For J = 0  To 8 
                S &= ReturnNumString(_Num(I * 9 + J)) 
             Next 
            S &= vbNewLine 
         Next 
         Return S 
     End  Function

返回整个数独的状态文本

 


     Private  Sub AppendString( ByVal Text  As  StringOptional  ByVal AppendMatrix  As  Boolean =  True
         If _HasString =  False  Then  Exit Sub 
        _S.AppendLine(Text) 
        _S.AppendLine() 
         If AppendMatrix =  True  Then 
            _S.AppendLine(ReturnMatrix) 
            _S.AppendLine() 
         End  If 
     End  Sub

将文本添加到文本对象,并根据AppendMatrix参数来决定是否将整个数独的状态添加到文本对象

 


     Private  Function IndexToXY( ByVal Index  As  IntegerAs  String 
         Return (Int(Index / 9) + 1) &  "-" & (Index  Mod 9 + 1) &  " Num:" & -_Num(Index) 
     End  Function

返回指定Index的坐标和已填的数,用于在文本对象中

 


     Public  Function CalculationString()  As  String 
         Return _S.ToString 
     End  Function

对外公开的方法,返回文本对象,也就是之前记录的求解过程,共日后研究参考

 

 

 

主求解函数——算法的核心

下面的3个函数是算法的核心


     Private  Function FindMinCell()  As  Integer 
         Dim I  As  Integer, C  As  Integer 
         Dim tP  As  Integer = -1, tMin  As  Integer = 20 

        I = 0 

         Do 
             If _Num(I) > 0  Then 
                C = Get1Count(_Num(I)) 
                 If C = 1  Then 
                     If SetNumPri(I, _Num(I)) =  False  Then  Return -2 

                    AppendString( "SetNum " & IndexToXY(I)) 

                     If I = tP  Then 
                        tP = -1 
                        tMin = 20 
                     End  If 

                    I = -1 
                 Else 
                     If C < tMin  Then 
                        tP = I 
                        tMin = C 
                     End  If 
                 End  If 
             End  If 
            I += 1 
         Loop  Until I > 80 

         Return tP 
     End  Function

该函数是获得最少可能数的单元格(可填数大于2的空白单元格)

该函数返回值有3个可能性

返回值:-1,没有找到这样的单元格,函数从某个唯一数单元格开始填数,依次填下去,并且把所有的空白单元格都填满。这说明,求解结束。

返回值:-2,没有找到这样的单元格,函数从某个唯一数单元格开始填数,依次填下去,产生了无解单元格。说明之前的求解过程有错误或者说该数独无解

返回值:0-80,找到这样的单元格,并且当前的数独数组中不再存在唯一数单元格(函数直接会在唯一数单元格上填数)

 

 


     Public  Function Calculate()  As  Integer() 
         Dim I  As  Integer 
         Dim K  As  Integer 
         Dim Q  As  New  Stack( Of  List( Of  Integer)) 
         Dim L  As  List( Of  Integer

        _S =  New System.Text. StringBuilder 
        AppendString( "Init Matrix"

        K = FindMinCell() 

         Do  While K <> -1 
             If K = -2  Then 
                 If Q.Count = 0  Then 
                    AppendString( "Error!!!!!"False
                     Return  Nothing 
                 End  If 


                L = Q.Pop 

                K = L(82) 
                L.RemoveAt(82) 

                I = L(81) + 1 
                L.RemoveAt(81) 

                AppendString( "Stack Pop " & Q.Count + 1,  False

                RestoreNum(L) 

                K = FindNextK(Q, L, K, I) 

             Else 
                L =  New  List( Of  Integer
                L.AddRange(_Num) 

                K = FindNextK(Q, L, K, 1) 

             End  If 

         Loop 

        AppendString( "Calculating Complete!!!!"

         Dim V(80)  As  Integer 
         For I = 0  To 80 
            V(I) = -_Num(I) 
         Next 
         Return V 
     End  Function

对外公开的主求解函数,返回最终结果的整形数组

首先解释一下栈对象Q,由于栈Q每次压栈的时候只能压一个对象,而当出现无唯一数单元格的情况的时候,需要将当前的数据缓存起来。需要缓存的内容有三个部分,分别是数独数组、找到的最少可能数的单元格的下标、最少可能数的单元格的选择填的第几个数。故用一个List(of Integer)对象将之前的三个内容缓存起来。L(0)—L(80)表示是数独数组,L(81)是最少可能数的单元格的下标,L(82)是最少可能数的单元格的选择填的第几个数。

该函数的主要是判断K的值,如上个函数所述,K的值主要有3种

K=-1,说明没有空白单元格,数独已经完美的求解完成,直接返回结果

K=-2,说明有无解单元格,那么判断栈Q中的数据,如果栈Q中没有数据,说明该数独无解;如果栈Q中有数据,那么把数据提出来,把数独的状态恢复到之前的情况。并从上次缓存的最少可能数单元格中,提取下一个可填数去继续进行尝试。

举例说明,缓存了0,1。说明上次尝试的是第1个单元格(下标从0开始)的第1个可填数。由于出现了无解单元格,说明第1个可填数是不正确的,那么继续尝试第2个可填数。调用的方法:FindNextK(Q, L, K, I),之前I已经加过1了。

K=0-80,得到最少可能数的单元格的下标。从该单元格的第1个可填数开始尝试。调用的方法:FindNextK(Q, L, K, 1)

尝试可能数的函数是FindNextK,返回值也是分为3种,-1、-2、0-80。意义和上面一样

 

 


     Private  Function FindNextK( ByVal Q  As  Stack( Of  List( Of  Integer)),  ByVal L  As  List( Of  Integer),  ByVal K  As  IntegerByVal Index  As  IntegerAs  Integer 

         Dim J  As  Integer = GetIndexOfNum(_Num(K), Index) 

         Do  While J <> -1 
             If SetNumPri(K, _V(J - 1)) =  True  Then 
                AppendString( "Stack Push " & Q.Count + 1,  False
                AppendString( "SetNum MayBe " & IndexToXY(K)) 

                L.Add(Index) 
                L.Add(K) 
                Q.Push(L) 

                K = FindMinCell() 

                 Exit Do 
             End  If 

            RestoreNum(L) 
            Index += 1 
            J = GetIndexOfNum(_Num(K), Index) 
         Loop 
         If J = -1  Then K = -2 
         Return K 
     End  Function

辅助函数,获得尝试可能数的结果

首先,通过GetIndexOfNum获得当前可填数。如果返回值-1的话,说明当前已经没有可填数,出现无解单元格,直接返回值为-2

然后尝试在当前单元格填数,调用SetNumPri(K, _V(J - 1)),返回True表示该数能填,那么把当前的状态缓存到栈Q中,并通过FindMinCell函数获得下一个可能的K值,并返回;返回False表示该数不能填,恢复数据到数独数组,继续尝试下一个数。

 

 

至此该算法类的代码都说明完整了

在该算法中仅仅用了最基本的解法——摒除法。遇见唯一数单元格,就直接填数,如果遇见无唯一数单元格,则缓存数据,并对该单元格的所有可填数做尝试,直到求解出该数独为止。

会有人疑问,利用栈Q缓存数据,会不会极大的占用系统资源,导致无法解题的情况。以目前的情况来看,我用该算法求解了“程序员们都是不被世人所理解的真正天才吗?-请大家看这个数独的解法”中的号称最难的数独,并把求解的结果保存到文件后打开分析了一下,发现栈Q的缓存不超过20步,以20步为例,每步83*4字节,则一共20*83*4=6640字节<7K字节。远小于系统的承受能力。因此,不必担心系统的承受能力

 

如果,谁有好的数独的算法,欢迎交流,不吝赐教。

 

 

让我们实战看看成果,用该算法求解本文开头的数独,代码如下:

Dim tS As New clsSudoku

tS.SetLine(1, 0, 6, 0, 5, 9, 3, 0, 0, 0) 
tS.SetLine(2, 9, 0, 1, 0, 0, 0, 5, 0, 0) 
tS.SetLine(3, 0, 3, 0, 4, 0, 0, 0, 9, 0) 
tS.SetLine(4, 1, 0, 8, 0, 2, 0, 0, 0, 4) 
tS.SetLine(5, 4, 0, 0, 3, 0, 9, 0, 0, 1) 
tS.SetLine(6, 2, 0, 0, 0, 1, 0, 6, 0, 9) 
tS.SetLine(7, 0, 8, 0, 0, 0, 6, 0, 2, 0) 
tS.SetLine(8, 0, 0, 4, 0, 0, 0, 8, 0, 7) 
tS.SetLine(9, 0, 0, 0, 7, 8, 5, 0, 1, 0)

tS.Calculate()

My.Computer.FileSystem.WriteAllText("1.txt", tS.CalculationString, False)

 

 

该数独还是比较简单的,一路唯一数单元格到底

 

结果如下:

Calculating Complete!!!!

#7        #6        #2        #5        #9        #3        #1        #4        #8        
#9        #4        #1        #2        #7        #8        #5        #3        #6        
#8        #3        #5        #4        #6        #1        #7        #9        #2        
#1        #9        #8        #6        #2        #7        #3        #5        #4        
#4        #7        #6        #3        #5        #9        #2        #8        #1        
#2        #5        #3        #8        #1        #4        #6        #7        #9        
#3        #8        #7        #1        #4        #6        #9        #2        #5        
#5        #1        #4        #9        #3        #2        #8        #6        #7        
#6        #2        #9        #7        #8        #5        #4        #1        #3      

 

 

 

 

 

下面是该算法类的完整代码


Public  Class  clsSudoku 
     Private _Num(80)  As  Integer 
     Private _V(9)  As  Integer 
     Private _S  As System.Text. StringBuilder 
     Private _HasString  As  Boolean 

     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 

     Private  Function Get1Count( ByVal Value  As  IntegerAs  Integer 
         Dim C  As  Integer = 0 
         Do  While Value > 0 
            Value = Value  And (Value - 1) 
            C += 1 
         Loop 
         Return C 
     End  Function 

     Private  Function RemoveNum( ByVal Row  As  IntegerByVal Col  As  IntegerByVal Num2  As  IntegerAs  Integer 
         Dim Index  As  Integer = Row * 9 + Col 
         If _Num(Index) > 0  Then _Num(Index) = _Num(Index)  And Num2 
         Return _Num(Index) 
     End  Function 

     Public  Function SetNum( ByVal Row  As  IntegerByVal Col  As  IntegerByVal Num  As  IntegerAs  Boolean 
         Return SetNumPri(Row - 1, Col - 1, Num - 1) 
     End  Function 

     Public  Function SetLine( ByVal Row  As  IntegerByVal  ParamArray Num()  As  IntegerAs  Boolean 
         If Num.Length = 0  Then  Return  True 

         Dim I  As  Integer 

         For I = 0  To IIf(Num.Length - 1 > 8, 8, Num.Length - 1) 
             If Num(I) > 0  AndAlso SetNumPri(Row - 1, I, Num(I) - 1) =  False  Then  Return  False 
         Next 

         Return  True 

     End  Function 

     Private  Function SetNumPri( ByVal Row  As  IntegerByVal Col  As  IntegerByVal Num  As  IntegerAs  Boolean 
         If (_V(Num)  And _Num(Row * 9 + Col)) = 0  Then  Return  False 
        _Num(Row * 9 + Col) = -(Num + 1) 
        Num = _V(9) - _V(Num) 

         Dim I  As  Integer, J  As  Integer 

         For I = 0  To 8 
             If RemoveNum(I, Col, Num) = 0  Then  Return  False 
             If RemoveNum(Row, I, Num) = 0  Then  Return  False 
         Next 

         Dim R1  As  Integer = Int(Row / 3) * 3 
         Dim C1  As  Integer = Int(Col / 3) * 3 

         For I = R1  To R1 + 2 
             For J = C1  To C1 + 2 
                 If RemoveNum(I, J, Num) = 0  Then  Return  False 
             Next 
         Next 

         Return  True 
     End  Function 

     Private  Function SetNumPri( ByVal Index  As  IntegerByVal Num2  As  IntegerAs  Boolean 
         Dim Row  As  Integer = Int(Index / 9) 
         Dim Col  As  Integer = Index  Mod 9 
         Dim I  As  Integer 
         For I = 0  To 8 
             If _V(I) = Num2  Then  Exit For 
         Next 
         Return SetNumPri(Row, Col, I) 
     End  Function 

     Private  Function FindMinCell()  As  Integer 
         Dim I  As  Integer, C  As  Integer 
         Dim tP  As  Integer = -1, tMin  As  Integer = 20 

        I = 0 

         Do 
             If _Num(I) > 0  Then 
                C = Get1Count(_Num(I)) 
                 If C = 1  Then 
                     If SetNumPri(I, _Num(I)) =  False  Then  Return -2 

                    AppendString( "SetNum " & IndexToXY(I)) 

                     If I = tP  Then 
                        tP = -1 
                        tMin = 20 
                     End  If 

                    I = -1 
                 Else 
                     If C < tMin  Then 
                        tP = I 
                        tMin = C 
                     End  If 
                 End  If 
             End  If 
            I += 1 
         Loop  Until I > 80 

         Return tP 
     End  Function 

     Public  Function Calculate()  As  Integer() 
         Dim I  As  Integer 
         Dim K  As  Integer 
         Dim Q  As  New  Stack( Of  List( Of  Integer)) 
         Dim L  As  List( Of  Integer

        _S =  New System.Text. StringBuilder 
        AppendString( "Init Matrix"

        K = FindMinCell() 

         Do  While K <> -1 
             If K = -2  Then 
                 If Q.Count = 0  Then 
                    AppendString( "Error!!!!!"False
                     Return  Nothing 
                 End  If 


                L = Q.Pop 

                K = L(82) 
                L.RemoveAt(82) 

                I = L(81) + 1 
                L.RemoveAt(81) 

                AppendString( "Stack Pop " & Q.Count + 1,  False

                RestoreNum(L) 

                K = FindNextK(Q, L, K, I) 

             Else 
                L =  New  List( Of  Integer
                L.AddRange(_Num) 

                K = FindNextK(Q, L, K, 1) 

             End  If 

         Loop 

        AppendString( "Calculating Complete!!!!"

         Dim V(80)  As  Integer 
         For I = 0  To 80 
            V(I) = -_Num(I) 
         Next 
         Return V 
     End  Function 

     Private  Sub RestoreNum( ByVal L  As  List( Of  Integer)) 
         Dim I  As  Integer 
         For I = 0  To 80 
            _Num(I) = L.Item(I) 
         Next 

        AppendString( "Restore Matrix"
     End  Sub 

     Private  Function GetIndexOfNum( ByVal Num  As  IntegerByVal Index  As  IntegerAs  Integer 
         Dim I  As  Integer, K  As  Integer = 0 
         For I = 0  To 8 
             If (_V(I)  And Num) <> 0  Then 
                K += 1 
                 If K = Index  Then  Return I + 1 
             End  If 
         Next 
         Return -1 
     End  Function 

     Private  Function FindNextK( ByVal Q  As  Stack( Of  List( Of  Integer)),  ByVal L  As  List( Of  Integer),  ByVal K  As  IntegerByVal Index  As  IntegerAs  Integer 

         Dim J  As  Integer = GetIndexOfNum(_Num(K), Index) 

         Do  While J <> -1 
             If SetNumPri(K, _V(J - 1)) =  True  Then 
                AppendString( "Stack Push " & Q.Count + 1,  False
                AppendString( "SetNum MayBe " & IndexToXY(K)) 

                L.Add(Index) 
                L.Add(K) 
                Q.Push(L) 

                K = FindMinCell() 

                 Exit Do 
             End  If 

            RestoreNum(L) 
            Index += 1 
            J = GetIndexOfNum(_Num(K), Index) 
         Loop 
         If J = -1  Then K = -2 
         Return K 
     End  Function 

     Private  Function ReturnNumString( ByVal Num  As  IntegerAs  String 
         If Num < 0  Then  Return  "#" & (-Num) &  " " 
         Dim I  As  Integer, S  As  String =  "" 
         For I = 0  To 8 
             If (_V(I)  And Num) <> 0  Then S &= (I + 1) 
         Next 
         Return S.PadRight(10) 
     End  Function 

     Private  Function ReturnMatrix()  As  String 
         Dim I  As  Integer, J  As  Integer, S  As  String =  "" 
         For I = 0  To 8 
             For J = 0  To 8 
                S &= ReturnNumString(_Num(I * 9 + J)) 
             Next 
            S &= vbNewLine 
         Next 
         Return S 
     End  Function 

     Private  Sub AppendString( ByVal Text  As  StringOptional  ByVal AppendMatrix  As  Boolean =  True
         If _HasString =  False  Then  Exit Sub 
        _S.AppendLine(Text) 
        _S.AppendLine() 
         If AppendMatrix =  True  Then 
            _S.AppendLine(ReturnMatrix) 
            _S.AppendLine() 
         End  If 
     End  Sub 

     Private  Function IndexToXY( ByVal Index  As  IntegerAs  String 
         Return (Int(Index / 9) + 1) &  "-" & (Index  Mod 9 + 1) &  " Num:" & -_Num(Index) 
     End  Function 

     Public  Function CalculationString()  As  String 
         Return _S.ToString 
     End  Function 
End  Class 

    本文转自万仓一黍博客园博客,原文链接:http://www.cnblogs.com/grenet/p/3138654.html ,如需转载请自行联系原作者
相关文章
机器学习/深度学习 算法 自动驾驶
520 0
|
3月前
|
算法 API 数据安全/隐私保护
深度解析京东图片搜索API:从图像识别到商品匹配的算法实践
京东图片搜索API基于图像识别技术,支持通过上传图片或图片URL搜索相似商品,提供智能匹配、结果筛选、分页查询等功能。适用于比价、竞品分析、推荐系统等场景。支持Python等开发语言,提供详细请求示例与文档。
|
6月前
|
监控 算法 安全
公司电脑监控软件关键技术探析:C# 环形缓冲区算法的理论与实践
环形缓冲区(Ring Buffer)是企业信息安全管理中电脑监控系统设计的核心数据结构,适用于高并发、高速率与短时有效的多源异构数据处理场景。其通过固定大小的连续内存空间实现闭环存储,具备内存优化、操作高效、数据时效管理和并发支持等优势。文章以C#语言为例,展示了线程安全的环形缓冲区实现,并结合URL访问记录监控应用场景,分析了其在流量削峰、关键数据保护和高性能处理中的适配性。该结构在日志捕获和事件缓冲中表现出色,对提升监控系统效能具有重要价值。
165 1
|
7月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
185 17
|
7月前
|
存储 算法 安全
如何控制上网行为——基于 C# 实现布隆过滤器算法的上网行为管控策略研究与实践解析
在数字化办公生态系统中,企业对员工网络行为的精细化管理已成为保障网络安全、提升组织效能的核心命题。如何在有效防范恶意网站访问、数据泄露风险的同时,避免过度管控对正常业务运作的负面影响,构成了企业网络安全领域的重要研究方向。在此背景下,数据结构与算法作为底层技术支撑,其重要性愈发凸显。本文将以布隆过滤器算法为研究对象,基于 C# 编程语言开展理论分析与工程实践,系统探讨该算法在企业上网行为管理中的应用范式。
199 8
|
7月前
|
存储 监控 算法
基于 C# 时间轮算法的控制局域网上网时间与实践应用
在数字化办公与教育环境中,局域网作为内部网络通信的核心基础设施,其精细化管理水平直接影响网络资源的合理配置与使用效能。对局域网用户上网时间的有效管控,已成为企业、教育机构等组织的重要管理需求。这一需求不仅旨在提升员工工作效率、规范学生网络使用行为,更是优化网络带宽资源分配的关键举措。时间轮算法作为一种经典的定时任务管理机制,在局域网用户上网时间管控场景中展现出显著的技术优势。本文将系统阐述时间轮算法的核心原理,并基于 C# 编程语言提供具体实现方案,以期深入剖析该算法在局域网管理中的应用逻辑与实践价值。
176 5
|
9月前
|
算法 Java
算法系列之回溯算法求解数独及所有可能解
数独求解的核心算法是回溯算法。回溯算法是一种通过逐步构建解决方案并在遇到冲突时回退的算法。具体来说,我们尝试在空格中填入一个数字,然后递归地继续填充下一个空格。如果在某个步骤中发现无法继续填充,则回退到上一步并尝试其他数字。
345 11
算法系列之回溯算法求解数独及所有可能解
|
12月前
|
机器学习/深度学习 人工智能 算法
深入解析图神经网络:Graph Transformer的算法基础与工程实践
Graph Transformer是一种结合了Transformer自注意力机制与图神经网络(GNNs)特点的神经网络模型,专为处理图结构数据而设计。它通过改进的数据表示方法、自注意力机制、拉普拉斯位置编码、消息传递与聚合机制等核心技术,实现了对图中节点间关系信息的高效处理及长程依赖关系的捕捉,显著提升了图相关任务的性能。本文详细解析了Graph Transformer的技术原理、实现细节及应用场景,并通过图书推荐系统的实例,展示了其在实际问题解决中的强大能力。
1475 30
|
12月前
|
存储 算法
深入解析PID控制算法:从理论到实践的完整指南
前言 大家好,今天我们介绍一下经典控制理论中的PID控制算法,并着重讲解该算法的编码实现,为实现后续的倒立摆样例内容做准备。 众所周知,掌握了 PID ,就相当于进入了控制工程的大门,也能为更高阶的控制理论学习打下基础。 在很多的自动化控制领域。都会遇到PID控制算法,这种算法具有很好的控制模式,可以让系统具有很好的鲁棒性。 基本介绍 PID 深入理解 (1)闭环控制系统:讲解 PID 之前,我们先解释什么是闭环控制系统。简单说就是一个有输入有输出的系统,输入能影响输出。一般情况下,人们也称输出为反馈,因此也叫闭环反馈控制系统。比如恒温水池,输入就是加热功率,输出就是水温度;比如冷库,
1633 15
|
机器学习/深度学习 算法 数据建模
计算机前沿技术-人工智能算法-生成对抗网络-算法原理及应用实践
计算机前沿技术-人工智能算法-生成对抗网络-算法原理及应用实践

热门文章

最新文章