经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
关于一个最简单的数独解题实现与疑惑一
来源:cnblogs  作者:punisher_cn  时间:2018/11/2 9:20:06  对本文有异议

一、缘起

  之前买了一本《算法的乐趣》,这么多日子里根本没看过。我可能是一个书籍的收藏者而不是读者,因为在办公室里的书架上琳琅满目的摆放了几十本书了,可所读者寥寥无几!言归正传,偶然看了这本书中关于数独的章节,觉得有意思,但书中代码不全,所以自己动手试试,看看能不能按照原作者的思路把这个问题解决了。

 二、编码

  1、首先说我自己是一个非常业余的编程爱好者,既不是本专业,也不从事相关工作,所以代码中肯定有很多乱七八糟的写法,如果有人看到后想揍人,那么。。。。。。

  1.         /// <summary>/// 关于单元格的类/// </summary>        [Serializable]public class SudokuCell
  2.         {public int num;        // 该单元格的值public bool isFixed;   // 该单元格是否是确定的数值public List<int> candidatures ;  // 候选数列表public SudokuCell()
  3.             {
  4.                 candidatures = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };  // 候选数列表            }
  5.         }

  这里的思想是这样的,对于数独游戏而言(这里只考虑9*9),有81个格子。每个格子,可以认为是一个单元格。这里用SudokuCell这个类来表示,同时,用一个候选数列表来表示该单元格还可以填入的数字。

  2、再定义一个关于整个盘面的类,表示数独游戏自身,因为内容较多,我分开来写

  1.      /// <summary>/// 关于整个盘面的类/// </summary>        [Serializable]public class SudokuGame
  2.         {
  3.         ......  
  4.      }

  2.1、SudokuGame类中,定义几个数据成员

  1.       // 定义单元格数组,表示81个单元格 public SudokuCell[,] cells = new SudokuCell[9,9];         // 记录已经确认的单元格的数量,就是那些数字已经确认无误的单元格的数量 public int fixedCount;

  2.2、构造函数

  1.               [] data =  ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ( i = ; i < ; i++ num = data[i];                  cells[/ , i % ] = / , i % ].num = num;       (num != / , i % ].isFixed = ++

  2.3、最终盘面输出

  1.  1             /// <summary> 2             ///  输出得到的结果 3             /// </summary> 4             public void WriteResult() 5             { 6                 Console.WriteLine("当前的fixedCount为:{0},局面为:",fixedCount); 7                 // 这里因为已知是9*9的数组 8                 9                 for (int i = 0; i < 9; i++)10                 {11                     for (int j = 0; j < 9; j++)12                     {13                         string s = cells[i, j].num.ToString();14                         Console.Write(s + " ");15                     }16                     // 输出换行17                     Console.WriteLine();    
  2. 18                 }19             }

  2.4 、传入一个需要判定的位置,看该位置是否已经有确定的值,有的话就跳到下一个位置

  1.             /// <summary>///  跳过已被确定好了数字的位置/// </summary>/// <param name="sp">需要判断的位置,0-80 </param>/// <returns>比传入的位置+1的位置</returns>public int SkipFixedCell(int sp)
  2.             {// 判断传入的位置是否在数组范围内if (sp<0 || sp >=81)
  3.                 {return 81;  // 因为从0开始,sp=80的时候是最后一个位置,这个位置的数据是需要处理的,处理之后,sp就变为81了                }// 这里,要把这个sp修改为行列值int row = sp / 9;int col = sp % 9;if (cells[row,col].isFixed == true)
  4.                 {// 如果当前位置已被确定,继续判定下一个位置SkipFixedCell(++sp);
  5.                 }return sp;
  6.             }

  2.5 、设置某个单元格的值

  1.             /// <summary>///  将某个值,设置到某个指定的位置,并进行检验/// </summary>/// <param name="row">需要设置的行号</param>/// <param name="col">需要设置的列号</param>/// <param name="num">需要设置的值</param>/// <returns></returns>public bool SetCandidatureToFixed(int row,int col,int num)
  2.             {//1、设置值cells[row, col].num = num;// 确定已确认的数字cells[row, col].isFixed = true;//2、排除相关20个格中候选数中的num值if (!ExclusiveCorrelativeCandidatures(row, col, num))
  3.                 {return false;
  4.                 }//3、查看相关20格在删除num之后的状态,并且如果触发了唯一值,则继续进行递归if (!ProcessSinglesCandidature(row, col))
  5.                 {return false;
  6.                 }//4、到这里,说明前面填入的num没有问题,可以确定这个单元格的数字了fixedCount++;return true;
  7.  
  8.             }

  2.6、删除相关20格的函数

  1.   1             /// <summary>  2             /// 在某个单元格的相关20格中删除该单元格已经确定的数字  3             /// </summary>  4             /// <param name="row">该单元格所在的行</param>  5             /// <param name="col">该单元格所在的列</param>  6             /// <param name="num">该单元格的填充值</param>  7             /// <returns>要删除的数字如果不存在,则证明错误,返回false</returns>  8             public bool ExclusiveCorrelativeCandidatures(int row,int col,int num)  9             { 10                 // 遍历行 11                 for (int currentCol = 0; currentCol < 9; currentCol++) 12                 { 13                     // 传入的数字的当前数的位置自然要跳过的,自己和自己比是没有意义的 14                     if (currentCol == col) 15                     { 16                         continue; 17                     } 18                     // 如果当前单元格未确定数字 19                     if (!cells[row, currentCol].isFixed ) 20                     { 21                         //如果候选数中存在这个要删除的数字 22                         if (cells[row, currentCol].candidatures.Contains(num)) 23                         { 24                             // 如果要删除的数字是这个候选数列表的最后一个数字了 25                             // 那么删除的话,导致候选数列表为空,则表示数据填入错误了 26                             if (cells[row, currentCol].candidatures.Count == 1) 27                             { 28                                 return false; 29                             } 30                             // 从候选数列表中删除这个指定的数字 31                             cells[row, currentCol].candidatures.Remove(num); 32                         } 33                     } 34                     else 35                     { 36                         // 对于已经确定了数字的单元格,则要比较是否与新填入的数字一致 37                         if (cells[row, currentCol].num == num) 38                         { 39                             return false; 40                         } 41                     } 42                 } 43                 // 遍历列 44                 for (int currentRow = 0; currentRow < 9; currentRow++) 45                 { 46                     if (currentRow == row) 47                     { 48                         continue; 49                     } 50                     // 如果当前单元格未确定数字 51                     if (!cells[currentRow, col].isFixed) 52                     { 53                         //如果候选数中存在这个要删除的数字 54                         if (cells[currentRow, col].candidatures.Contains(num)) 55                         { 56                             // 如果要删除的数字是这个候选数列表的最后一个数字了 57                             // 那么删除的话,导致候选数列表为空,则表示数据填入错误了 58                             if (cells[currentRow, col].candidatures.Count == 1) 59                             { 60                                 return false; 61                             } 62                             // 从候选数列表中删除这个指定的数字 63                             cells[currentRow, col].candidatures.Remove(num); 64                         } 65                     } 66                     else 67                     { 68                         // 对于已经确定了数字的单元格,则要比较是否与新填入的数字一致 69                         if (cells[currentRow, col].num == num) 70                         { 71                             return false; 72                         } 73                     } 74                 } 75                 // 遍历所在的九宫格 76                 // 这里,可以把81格看成9个9*9的九宫格,那么根据row、col的值,让其除以3,则可以得到 77                 // 0,0   0,1   0,2 78                 // 1,0   1,1   1,2 79                 // 2,0   2,1   2,2 80                 // 得到这样的位置信息 81                 // 再进一步,这里用一个3维数组来表示怎么样?这里估计可能是一个笨方法 82                 // 我的想法是可以避免去判断某个位置的九宫 83                 int[,][] arr = new int[3,3][]; 84                 arr[0, 0] = new int[] { 0, 1, 2,  9, 10, 11, 18, 19, 20 }; 85                 arr[0, 1] = new int[] { 3, 4, 5, 12, 13, 14, 21, 22, 23 }; 86                 arr[0, 2] = new int[] { 6, 7, 8, 15, 16, 17, 24, 25, 26 }; 87                 arr[1, 0] = new int[] { 27, 28, 29, 36, 37, 38, 45, 46, 47 }; 88                 arr[1, 1] = new int[] { 30, 31, 32, 39, 40, 41, 48, 49, 50 }; 89                 arr[1, 2] = new int[] { 33, 34, 35, 42, 43, 44, 51, 52, 53 }; 90                 arr[2, 0] = new int[] { 54, 55, 56, 63, 64, 65, 72, 73, 74 }; 91                 arr[2, 1] = new int[] { 57, 58, 59, 66, 67, 68, 75, 76, 77 }; 92                 arr[2, 2] = new int[] { 60, 61, 62, 69, 70, 71, 78, 79, 80 }; 93  94                 // 获取给定的点所在的位置,可以从上述的二维数组中定位 95                 int r = row / 3; 96                 int c = col / 3; 97  98                 // 根据二维数据的定位,获取了在3维数组中的值 99                 for (int i = 0; i < 9; i++)100                 {101                     int indexOfAll = arr[r, c][i];102                     // 把诸如14,21这样的值再转化为row、col形式103                     int rr = indexOfAll / 9;104                     int cc = indexOfAll % 9;105 106                     // 判断是否是当前位置107                     if (rr == row && cc == col)108                     {109                         continue;110                     }111 112                     if (!cells[rr, cc].isFixed)113                     {114                         //如果候选数中存在这个要删除的数字115                         if (cells[rr, cc].candidatures.Contains(num))116                         {117                             // 如果要删除的数字是这个候选数列表的最后一个数字了118                             // 那么删除的话,导致候选数列表为空,则表示数据填入错误了119                             if (cells[rr, cc].candidatures.Count == 1)120                             {121                                 return false;122                             }123                             // 从候选数列表中删除这个指定的数字124                             cells[rr, cc].candidatures.Remove(num);125                         }126                     }127                     else128                     {129                         // 对于已经确定了数字的单元格,则要比较是否与新填入的数字一致130                         if (cells[rr, cc].num == num)131                         {132                             return false;133                         }134                     }135                 }136                 return true;137             }

  2.7、查看相关20格函数,看是否有唯一解出现

  1.   1             /// <summary>  2             /// 查看相关20格的状态,是否存在唯一候选数,存在,则继续调用SetCandidatureToFixed  3             /// </summary>  4             /// <param name="row">该单元格所在的行</param>  5             /// <param name="col">该单元格所在的列</param>  6             /// <returns>是否存在错误</returns>  7             public bool ProcessSinglesCandidature(int row, int col)  8             {  9                 // ExclusiveCorrelativeCandidatures,该函数保证了候选数至少会存在一个 10                 // 遍历行 11                 for (int currentCol = 0; currentCol < 9; currentCol++) 12                 { 13                     // 需要判断是否是当前的位置 14                     if (currentCol == col) 15                     { 16                         continue; 17                     } 18                     // 如果当前单元格的数字没有确定,而且只有一个候选数 19                     if (!cells[row, currentCol].isFixed && cells[row, currentCol].candidatures.Count == 1) 20                     { 21                         // 那么就应该把这个数字确定,并且以它为原则,继续确认其他数字 22                         int num = cells[row, currentCol].candidatures[0]; 23  24                         // 递归调用,继续搜寻 25                         //fixedCount++; 26                         //if (!ProcessSinglesCandidature(row, currentCol)) 27                         //{ 28                         //    return false; 29                         //} 30  31  32                         if (!SetCandidatureToFixed(row, currentCol, num)) 33                         { 34                             return false; 35                         } 36                     } 37                 } 38                 // 遍历列 39                 for (int currentRow = 0; currentRow < 9; currentRow++) 40                 { 41                     // 需要判断是否是当前的位置 42                     if (currentRow == row) 43                     { 44                         continue; 45                     } 46                     // 如果当前单元格的数字没有确定,而且只有一个候选数 47                     if (!cells[currentRow, col].isFixed && cells[currentRow, col].candidatures.Count == 1) 48                     { 49                         // 那么就应该把这个数字确定,并且以它为原则,继续确认其他数字 50                         int num = cells[currentRow, col].candidatures[0]; 51                         // 递归调用,继续搜寻 52                         //fixedCount++; 53                         //if (!ProcessSinglesCandidature(currentRow, col)) 54                         //{ 55                         //    return false; 56                         //} 57  58                         if (!SetCandidatureToFixed(currentRow, col, num)) 59                         { 60                             return false; 61                         } 62                     } 63                 } 64                 // 遍历所在的九宫格 65                 // 这里,可以把81格看成9个9*9的九宫格,那么根据row、col的值,让其除以3,则可以得到 66                 // 0,0   0,1   0,2 67                 // 1,0   1,1   1,2 68                 // 2,0   2,1   2,2 69                 // 得到这样的位置信息 70                 // 再进一步,这里用一个3维数组来表示怎么样? 71                 // 我的想法是可以避免去判断某个位置的九宫 72                 int[,][] arr = new int[3, 3][]; 73                 arr[0, 0] = new int[] { 0, 1, 2, 9, 10, 11, 18, 19, 20 }; 74                 arr[0, 1] = new int[] { 3, 4, 5, 12, 13, 14, 21, 22, 23 }; 75                 arr[0, 2] = new int[] { 6, 7, 8, 15, 16, 17, 24, 25, 26 }; 76                 arr[1, 0] = new int[] { 27, 28, 29, 36, 37, 38, 45, 46, 47 }; 77                 arr[1, 1] = new int[] { 30, 31, 32, 39, 40, 41, 48, 49, 50 }; 78                 arr[1, 2] = new int[] { 33, 34, 35, 42, 43, 44, 51, 52, 53 }; 79                 arr[2, 0] = new int[] { 54, 55, 56, 63, 64, 65, 72, 73, 74 }; 80                 arr[2, 1] = new int[] { 57, 58, 59, 66, 67, 68, 75, 76, 77 }; 81                 arr[2, 2] = new int[] { 60, 61, 62, 69, 70, 71, 78, 79, 80 }; 82  83                 // 获取给定的点所在的位置,可以从上述的二维数组中定位 84                 int r = row / 3; 85                 int c = col / 3; 86  87                 // 根据二维数据的定位,获取了在3维数组中的值 88                 for (int i = 0; i < 9; i++) 89                 { 90                     int indexOfAll = arr[r, c][i]; 91                     // 把诸如14,21这样的值再转化为row、col形式 92                     int rr = indexOfAll / 9; 93                     int cc = indexOfAll % 9; 94                     // 需要判断是否是当前的位置 95                     if (rr == row && cc == col) 96                     { 97                         continue; 98                     } 99                     if (!cells[rr, cc].isFixed && cells[rr, cc].candidatures.Count == 1)100                     {101                         // 那么就应该把这个数字确定,并且以它为原则,继续确认其他数字102                         int num = cells[rr, cc].candidatures[0];103                         // 递归调用,继续搜寻104 105                         //fixedCount++;106                         //if (!ProcessSinglesCandidature(rr, cc))107                         //{108                         //    return false;109                         //}110 111                         if (!SetCandidatureToFixed(rr, cc, num))112                         {113                             return false;114                         }115                     }116                 }117                 return true;118             }

   3、游戏类,问题在这里,随后再说

  1.  1         /// <summary> 2         ///  问题解决类 3         /// </summary> 4         public class Solution 5         { 6             public static int gameCount = 0; 7             // 3、类似于穷举的算法 8             /// <summary> 9             /// 求解算法,这是一个递归算法10             /// </summary>11             /// <param name="game">当前的局面</param>12             /// <param name="sp">查找的位置,从0开始,到80结束</param>13             public void FindSudokuSolution(SudokuGame game, int sp)14             {15                 // 0、设置递归算法的结束条件16                 if (game.fixedCount >= 95)17                 {18                     game.WriteResult();19                     return;20                 }21                 // 1、判断要查找的位置是否已经被确定22                 // 之前所使用函数,是因为可能存在连续被确定的情况23                 // 所以,下面这个函数也是一个递归函数24                 sp = game.SkipFixedCell(sp);25                 if (sp >= 81 || sp < 0)26                 {27                     // 如果已经超出了数组的范围,那么直接返回即可28                     return;29                 }30                 // 2、获取当前位置的cell31                 int row = sp / 9;32                 int col = sp % 9;33                 SudokuCell currentCell = new SudokuCell();34                 currentCell = game.cells[row, col];35 36                 // 3、定义一个新状态,用于保存当前的game的状态37                 SudokuGame newGameState = new SudokuGame();38 39                 // 40                 for (int i = 0; i < currentCell.candidatures.Count; i++)41                 {42                     newGameState = DeepCopyGame(game);    //把当前的状态保存一下43 44                    // Console.WriteLine("创建了{0}个局面了",gameCount++);45 46                     int currentCandidature = currentCell.candidatures.ElementAt(i);47                     if (newGameState.SetCandidatureToFixed(row, col, currentCandidature))48                     {49                         // 试数成功,没有冲突,进行下一个单元格50                         51                         FindSudokuSolution(newGameState, sp+1);52                     }53                 }54                 return;55             }56         }

  4、深拷贝类

  1.  1         /// <summary> 2         ///  通过序列化实现Game的深复制 3         /// </summary> 4         /// <param name="obj"></param> 5         /// <returns></returns> 6         public static SudokuGame DeepCopyGame(SudokuGame obj) 7         { 8             object retVal; 9             using (MemoryStream ms = new MemoryStream())10             {11                 BinaryFormatter bf = new BinaryFormatter();12                 //序列化13                 bf.Serialize(ms,obj);14                 ms.Seek(0,SeekOrigin.Begin);15                 // 反序列化16                 retVal = bf.Deserialize(ms);17                 ms.Close();18             }19             return (SudokuGame)retVal;20         }

  5、调用方式

  1. 1             SudokuGame game = new SudokuGame();2             Solution s = new Solution();3             s.FindSudokuSolution(game,0);

  6、疑惑与说明

  上面,就把所有的代码都完成了,试了几个局面,也能得到正确的结果。但有个问题让我百思不得其解。

在3中,有    if (game.fixedCount >= 95) 此句作为递归的结束条件。可是,这里这个数字不应该是81吗????但是,如果写81,最终输出的盘面中,会有一部分数字没有得到答案,而这个95,也是我通过一次次的摸索,根据最后能得到答案的结果来实现的。这里,因为是递归,所以局面太多,简单的调试根本进行不下去。。。。。。不知道最终的问题何在,后面还需要慢慢的摸索,更希望有哪位能给指点一下迷津,提前谢谢了。




 

 

 

 

 

  

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号