VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • C# 数独求解算法的实现(2)

大多数都有注释,配合注释应该不难理解,如有问题欢迎评论区交流。稍微说一下,重载ToString是为了方便调试和查看状态,其中空心方框表示未填写数字的单元格,数字表示题目给出数字的单元格,圈数字表示唯一数单元格填写的数字,括号数字表示有多个候选数通过尝试(暴力搜索)确定的数字。注意类文件最上面有一个using static System.Math; 导入静态类,不然每次调用数学函数都要 Math. ,很烦。

求解过程信息

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class PathTree
 {
  public PathTree Parent { get; set; }
  public List<PathTree> Children { get; } = new List<PathTree>();
 
  public SudokuBlock Block { get; }
  public int X { get; }
  public int Y { get; }
  public int Number { get; }
  public bool Pass { get; private set; } = true;
  public (byte x, byte y)[] SetList { get; set; }
 
  public PathTree(SudokuBlock block, int x, int y, int number)
  {
   Block = block;
   X = x;
   Y = y;
   Number = number;
 
  }
 
  public PathTree(SudokuBlock block, int row, int column, int number, PathTree parent)
   : this(block, row, column, number)
  {
   Parent = parent;
   Parent.Children.Add(this);
  }
 
  public void SetPass(bool pass)
  {
   Pass = pass;
  }
 }

其中记录了每个步骤在哪个单元格填写了哪个数字,上一步是哪一步,之后尝试过哪些步骤,这一步是否会导致之后的步骤出现死路,填写数字后影响到的单元格和候选数字(用来在悔步的时候恢复相应单元格的候选数字)。

答案存储

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class SudokuState
 {
  public SudokuBlock[][] SudokuBoard { get; }
  public SudokuState(SudokuSolver sudoku)
  {
   SudokuBoard = new SudokuBlock[sudoku.SudokuBoard.Length][];
   //初始化数独的行
   for (int i = 0; i < sudoku.SudokuBoard.Length; i++)
   {
    SudokuBoard[i] = new SudokuBlock[sudoku.SudokuBoard[i].Length];
    //初始化每行的列
    for (int j = 0; j < sudoku.SudokuBoard[i].Length; j++)
    {
     SudokuBoard[i][j] = new SudokuBlock(
      sudoku.SudokuBoard[i][j].IsCondition
      , null
      , sudoku.SudokuBoard[i][j].Number
      , (byte)i
      , (byte)j);
     if (sudoku.SudokuBoard[i][j].IsUnique)
     {
      SudokuBoard[i][j].SetUnique();
     }
    }
   }
  }
 
  public override string ToString()
  {
   static string Str(SudokuBlock b)
   {
    var n1 = new[] { "①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨" };
    var n2 = new[] { "⑴", "⑵", "⑶", "⑷", "⑸", "⑹", "⑺", "⑻", "⑼" };
    return b.Number.HasValue
     ? b.IsCondition
      ? " " + b.Number
      : b.IsUnique
       ? n1[b.Number.Value - 1]
       : n2[b.Number.Value - 1]
     : "▢";
   }
   return
$@"{Str(SudokuBoard[0][0])},{Str(SudokuBoard[0][1])},{Str(SudokuBoard[0][2])},{Str(SudokuBoard[0][3])},{Str(SudokuBoard[0][4])},{Str(SudokuBoard[0][5])},{Str(SudokuBoard[0][6])},{Str(SudokuBoard[0][7])},{Str(SudokuBoard[0][8])}
{Str(SudokuBoard[1][0])},{Str(SudokuBoard[1][1])},{Str(SudokuBoard[1][2])},{Str(SudokuBoard[1][3])},{Str(SudokuBoard[1][4])},{Str(SudokuBoard[1][5])},{Str(SudokuBoard[1][6])},{Str(SudokuBoard[1][7])},{Str(SudokuBoard[1][8])}
{Str(SudokuBoard[2][0])},{Str(SudokuBoard[2][1])},{Str(SudokuBoard[2][2])},{Str(SudokuBoard[2][3])},{Str(SudokuBoard[2][4])},{Str(SudokuBoard[2][5])},{Str(SudokuBoard[2][6])},{Str(SudokuBoard[2][7])},{Str(SudokuBoard[2][8])}
{Str(SudokuBoard[3][0])},{Str(SudokuBoard[3][1])},{Str(SudokuBoard[3][2])},{Str(SudokuBoard[3][3])},{Str(SudokuBoard[3][4])},{Str(SudokuBoard[3][5])},{Str(SudokuBoard[3][6])},{Str(SudokuBoard[3][7])},{Str(SudokuBoard[3][8])}
{Str(SudokuBoard[4][0])},{Str(SudokuBoard[4][1])},{Str(SudokuBoard[4][2])},{Str(SudokuBoard[4][3])},{Str(SudokuBoard[4][4])},{Str(SudokuBoard[4][5])},{Str(SudokuBoard[4][6])},{Str(SudokuBoard[4][7])},{Str(SudokuBoard[4][8])}
{Str(SudokuBoard[5][0])},{Str(SudokuBoard[5][1])},{Str(SudokuBoard[5][2])},{Str(SudokuBoard[5][3])},{Str(SudokuBoard[5][4])},{Str(SudokuBoard[5][5])},{Str(SudokuBoard[5][6])},{Str(SudokuBoard[5][7])},{Str(SudokuBoard[5][8])}
{Str(SudokuBoard[6][0])},{Str(SudokuBoard[6][1])},{Str(SudokuBoard[6][2])},{Str(SudokuBoard[6][3])},{Str(SudokuBoard[6][4])},{Str(SudokuBoard[6][5])},{Str(SudokuBoard[6][6])},{Str(SudokuBoard[6][7])},{Str(SudokuBoard[6][8])}
{Str(SudokuBoard[7][0])},{Str(SudokuBoard[7][1])},{Str(SudokuBoard[7][2])},{Str(SudokuBoard[7][3])},{Str(SudokuBoard[7][4])},{Str(SudokuBoard[7][5])},{Str(SudokuBoard[7][6])},{Str(SudokuBoard[7][7])},{Str(SudokuBoard[7][8])}
{Str(SudokuBoard[8][0])},{Str(SudokuBoard[8][1])},{Str(SudokuBoard[8][2])},{Str(SudokuBoard[8][3])},{Str(SudokuBoard[8][4])},{Str(SudokuBoard[8][5])},{Str(SudokuBoard[8][6])},{Str(SudokuBoard[8][7])},{Str(SudokuBoard[8][8])}";
  }
 }

没什么好说的,就是保存答案的,因为有些数独的解不唯一,将来有机会扩展求多解时避免相互覆盖。

还有一个辅助类,单元格定义

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class SudokuBlock
{
 /// <summary>
 /// 填入的数字
 /// </summary>
 public byte? Number { get; private set; }
 
 /// <summary>
 /// X坐标
 /// </summary>
 public byte X { get; }
 
 /// <summary>
 /// Y坐标
 /// </summary>
 public byte Y { get; }
 
 /// <summary>
 /// 候选数字,下标所示状态表示数字“下标加1”是否能填入
 /// </summary>
 public BitArray Candidate { get; private set; }
 
 /// <summary>
 /// 是否为条件(题目)给出数字的单元格
 /// </summary>
 public bool IsCondition { get; }
 
 /// <summary>
 /// 是否为游戏开始就能确定唯一可填数字的单元格
 /// </summary>
 public bool IsUnique { get; private set; }
 
 public SudokuBlock(bool isCondition, BitArray candidate, byte? number, byte x, byte y)
 {
  IsCondition = isCondition;
  Candidate = candidate;
  Number = number;
  IsUnique = false;
  X = x;
  Y = y;
 }
 
 public void SetNumber(byte? number)
 {
  Number = number;
 }
 
 public void SetCandidate(BitArray candidate)
 {
  Candidate = candidate;
 }
 
 public void SetUnique()
 {
  IsUnique = true;
 }
}

测试代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
static void Main(string[] args)
  {
   //模板
   //byte[][] game = new byte[][] {
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},
   // new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0},};
   //这个简单,无需尝试,一直填唯一数单元格,填完后剩下的单元格又有会变唯一数单元格
   //byte[][] game = new byte[][] {
   // new byte[]{0, 5, 0, 7, 0, 6, 0, 1, 0},
   // new byte[]{0, 8, 0, 0, 9, 0, 0, 6, 0},
   // new byte[]{0, 6, 9, 0, 8, 0, 7, 3, 0},
   // new byte[]{0, 1, 0, 0, 4, 0, 0, 0, 6},
   // new byte[]{6, 0, 7, 1, 0, 3, 8, 0, 5},
   // new byte[]{9, 0, 0, 0, 0, 8, 0, 2, 0},
   // new byte[]{0, 2, 4, 0, 1, 0, 6, 5, 0},
   // new byte[]{0, 7, 0, 0, 6, 0, 0, 4, 0},
   // new byte[]{0, 9, 0, 4, 0, 2, 0, 8, 0},};
   //可以填一部分唯一数单元格,剩下一部分需要尝试,调试用
   //byte[][] game = new byte[][] {
   // new byte[]{7, 0, 0, 5, 0, 0, 0, 0, 2},
   // new byte[]{0, 3, 0, 0, 0, 4, 6, 0, 0},
   // new byte[]{0, 0, 2, 6, 0, 0, 0, 0, 0},
   // new byte[]{2, 0, 0, 0, 7, 0, 0, 0, 5},
   // new byte[]{5, 0, 0, 1, 0, 3, 0, 0, 6},
   // new byte[]{3, 0, 0, 4, 0, 0, 0, 0, 9},
   // new byte[]{0, 0, 0, 0, 0, 1, 5, 0, 0},
   // new byte[]{0, 0, 7, 2, 0, 0, 0, 4, 0},
   // new byte[]{4, 0, 0, 0, 0, 9, 0, 0, 7},};
   //全部要靠尝试来填
   byte[][] game = new byte[][] {
    new byte[]{1, 0, 0, 2, 0, 0, 3, 0, 0},
    new byte[]{0, 4, 0, 5, 0, 0, 0, 6, 0},
    new byte[]{0, 0, 0, 7, 0, 0, 8, 0, 0},
    new byte[]{3, 0, 0, 0, 0, 7, 0, 0, 0},
    new byte[]{0, 9, 0, 0, 0, 0, 0, 5, 0},
    new byte[]{0, 0, 0, 6, 0, 0, 0, 0, 7},
    new byte[]{0, 0, 2, 0, 0, 4, 0, 0, 0},
    new byte[]{0, 5, 0, 0, 0, 6, 0, 9, 0},
    new byte[]{0, 0, 8, 0, 0, 1, 0, 0, 3},};
   var su = new SudokuSolver(game);
   var r = su.Solve();
   var r1 = r.First();
   static IEnumerable<PathTree> GetPath(PathTree pathTree)
   {
    List<PathTree> list = new List<PathTree>();
    var path = pathTree;
    while (path.Parent != null)
    {
     list.Add(path);
     path = path.Parent;
    }
 
    return list.Reverse<PathTree>();
   }
 
   var p = GetPath(r1.path).Select(x => $"在 {x.X + 1} 行 {x.Y + 1} 列填入 {x.Number}");
   foreach(var step in p)
   {
    Console.WriteLine(step);
   }
 
   Console.WriteLine(r1.sudoku);
   Console.ReadKey();
  }

结果预览:

上面还有,更多步骤,太长,就不全部截下来了。关于第二张图详情请看后面的总结部分。

总结

这个数独求解器运用了大量 C# 7 的新特性,特别是 本地函数 和 基于 Tulpe 的简写的多返回值函数,能把本来一团乱的代码理清楚,写清爽。 C# 果然是比 Java 这个躺在功劳簿上吃老本不求上进的坑爹语言爽多了。yield return 返回迭代器这种简直是神仙设计,随时想返回就返回,下次进来还能接着上次的地方继续跑,写这种代码简直爽翻。另外目前多解求解功能还不可用,只是预留了集合返回类型和相关参数,以后看情况吧。

如果你看过我的这篇文章.Net Core 3 骚操作 之 用 Windows 桌面应用开发 Asp.Net Core 网站,你也可以在发布启动网站后访问https://localhost/Sudoku来运行数独求解器,注意,调试状态下端口为5001。

  本文地址:https://www.cnblogs.com/coredx/p/12173702.html

  完整源代码:Github

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。


相关教程
关于我们--广告服务--免责声明--本站帮助-友情链接--版权声明--联系我们       黑ICP备07002182号