C#中怎么实现一个数独求解算法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
1、先寻找并填写那些唯一数单元格。在部分数独中有些单元格会因为同行、列、宫内题目已知数的限制,实际只有一个数可以填,这种单元格就应该趁早填好,因为没有尝试的必要,不提前处理掉还会影响之后求解的效率。在填写数字后,同行、列、宫的候选数就会减少,可能会出现新的唯一数单元格,那么继续填写,直到没有唯一数单元格为止。
2、检查是否已经完成游戏,也就是所有单元格都有数字。部分简单数独一直填唯一数单元格就可以完成游戏。
3、按照单元格从左到右、从上到下,数字从小到大的顺序尝试填写有多个候选数的单元格,直到全部填完或者发现有单元格候选数为空。如果出现无候选数的单元格说明之前填错数导致出现死路,就需要悔步清除上一个单元格填过的数,换成下一个候选数继续尝试。如果清除后发现没有更大的候选数可填,说明更早之前就已经填错了,要继续悔步并换下一个候选数。有可能需要连续悔多步,一直悔步直到有更大的候选数可填的单元格。如果一路到最开始的单元格都没法填,说明这个数独有问题,无解。
代码(包括数独求解器,求解过程信息,答案存储三个主要类):
数独求解器
public class SudokuSolver { /// <summary> /// 题目面板 /// </summary> public SudokuBlock[][] SudokuBoard { get; } public SudokuSolver(byte[][] board) { SudokuBoard = new SudokuBlock[board.Length][]; //初始化数独的行 for (int i = 0; i < board.Length; i++) { SudokuBoard[i] = new SudokuBlock[board[i].Length]; //初始化每行的列 for (int j = 0; j < board[i].Length; j++) { SudokuBoard[i][j] = new SudokuBlock( board[i][j] > 0 , board[i][j] <= 0 ? new BitArray(board.Length) : null , board[i][j] > 0 ? (byte?)board[i][j] : null , (byte)i , (byte)j); } } } /// <summary> /// 求解数独 /// </summary> /// <returns>获得的解</returns> public IEnumerable<(SudokuState sudoku, PathTree path)> Solve(bool multiAnswer = false) { //初始化各个单元格能填入的数字 InitCandidate(); var pathRoot0 = new PathTree(null, -1, -1, -1); //填写路径树,在非递归方法中用于记录回退路径和其他有用信息,初始生成一个根 var path0 = pathRoot0; //循环填入能填入的数字只有一个的单元格,每次填入都可能产生新的唯一数单元格,直到没有唯一数单元格可填 while (true) { if (!FillUniqueNumber(ref path0)) { break; } } //检查是否在填唯一数单元格时就已经把所有单元格填满了 var finish = true; foreach (var row in SudokuBoard) { foreach (var cell in row) { if (!cell.IsCondition && !cell.IsUnique) { finish = false; break; } } if (!finish) { break; } } if (finish) { yield return (new SudokuState(this), path0); yield break; } var pathRoot = new PathTree(null, -1, -1, -1); //填写路径树,在非递归方法中用于记录回退路径和其他有用信息,初始生成一个根 var path = pathRoot; var toRe = new List<(SudokuState sudoku, PathTree path)>(); //还存在需要试数才能求解的单元格,开始暴力搜索 int i = 0, j = 0; while (true) { (i, j) = NextBlock(i, j); //正常情况下返回-1表示已经全部填完 if (i == -1 && j == -1 && !multiAnswer) { var pathLast = path;//记住最后一步 var path2 = path; while(path2.Parent.X != -1 && path2.Parent.Y != -1) { path2 = path2.Parent; } //将暴力搜索的第一步追加到唯一数单元格的填写步骤的最后一步之后,连接成完整的填数步骤 path0.Children.Add(path2); path2.Parent = path0; yield return (new SudokuState(this), pathLast); break; } var numNode = path.Children.LastOrDefault(); //确定要从哪个数开始进行填入尝试 var num = numNode == null ? 0 : numNode.Number; bool filled = false; //是否发现可以填入的数 //循环查看从num开始接下来的候选数是否能填(num是最后一次填入的数,传到Candidate[]的索引器中刚好指向 num + 1是否能填的存储位,对于标准数独,候选数为 1~9,Candidate的索引范围就是 0~8) for (; !SudokuBoard[i][j].IsCondition && !SudokuBoard[i][j].IsUnique && num < SudokuBoard[i][j].Candidate.Length; num++) { //如果有可以填的候选数,理论上不会遇见没有可以填的情况,这种死路情况已经在UpdateCandidate时检查了 if (SudokuBoard[i][j].Candidate[num] && !path.Children.Any(x => x.Number - 1 == num && !x.Pass)) { filled = true; //进来了说明单元格有数可以填 //记录步骤 var node = new PathTree(SudokuBoard[i][j], i, j, num + 1, path); path = node; //如果更新相关单元格的候选数时发现死路(更新函数会在发现死路时自动撤销更新) (bool canFill, (byte x, byte y)[] setList) updateResult = UpdateCandidate(i, j, (byte)(num + 1)); if (!updateResult.canFill) { //记录这条路是死路 path.SetPass(false); } //仅在确认是活路时设置填入数字 if (path.Pass) { SudokuBoard[i][j].SetNumber((byte)(num + 1)); path.SetList = updateResult.setList;//记录相关单元格可填数更新记录,方便在回退时撤销更新 } else //出现死路,要进行回退,重试这个单元格的其他可填数字 { path.Block.SetNumber(null); path = path.Parent; } //填入一个候选数后跳出循环,不再继续尝试填入之后的候选数 break; } } if (!filled)//如果没有成功填入数字,说明上一步填入的单元格就是错的,会导致后面的单元格怎么填都不对,要回退到上一个单元格重新填 { path.SetPass(false); path.Block.SetNumber(null); foreach (var pos in path.SetList) { SudokuBoard[pos.x][pos.y].Candidate.Set(path.Number - 1, true); } path = path.Parent; i = path.X < 0 ? 0 : path.X; j = path.Y < 0 ? 0 : path.Y; } } } /// <summary> /// 初始化候选项 /// </summary> private void InitCandidate() { //初始化每行空缺待填的数字 var rb = new List<BitArray>(); for (int i = 0; i < SudokuBoard.Length; i++) { var r = new BitArray(SudokuBoard.Length); r.SetAll(true); for (int j = 0; j < SudokuBoard[i].Length; j++) { //如果i行j列是条件(题目)给出的数,设置数字不能再填(r[x] == false 表示 i 行不能再填 x + 1,下标加1表示数独可用的数字,下标对应的值表示下标加1所表示的数是否还能填入该行) if (SudokuBoard[i][j].IsCondition || SudokuBoard[i][j].IsUnique) { r.Set(SudokuBoard[i][j].Number.Value - 1, false); } } rb.Add(r); } //初始化每列空缺待填的数字 var cb = new List<BitArray>(); for (int j = 0; j < SudokuBoard[0].Length; j++) { var c = new BitArray(SudokuBoard[0].Length); c.SetAll(true); for (int i = 0; i < SudokuBoard.Length; i++) { if (SudokuBoard[i][j].IsCondition || SudokuBoard[i][j].IsUnique) { c.Set(SudokuBoard[i][j].Number.Value - 1, false); } } cb.Add(c); } //初始化每宫空缺待填的数字(目前只能算标准 n×n 数独的宫) var gb = new List<BitArray>(); //n表示每宫应有的行、列数(标准数独行列、数相同) var n = (int)Sqrt(SudokuBoard.Length); for (int g = 0; g < SudokuBoard.Length; g++) { var gba = new BitArray(SudokuBoard.Length); gba.SetAll(true); for (int i = g / n * n; i < g / n * n + n; i++) { for (int j = g % n * n; j < g % n * n + n; j++) { if (SudokuBoard[i][j].IsCondition || SudokuBoard[i][j].IsUnique) { gba.Set(SudokuBoard[i][j].Number.Value - 1, false); } } } gb.Add(gba); } //初始化每格可填的候选数字 for (int i = 0; i < SudokuBoard.Length; i++) { for (int j = 0; j < SudokuBoard[i].Length; j++) { if (!SudokuBoard[i][j].IsCondition) { var c = SudokuBoard[i][j].Candidate; c.SetAll(true); //当前格能填的数为其所在行、列、宫同时空缺待填的数字,按位与运算后只有同时能填的候选数保持1(可填如当前格),否则变成0 // i / n * n + j / n:根据行号列号计算宫号, c = c.And(rb[i]).And(cb[j]).And(gb[i / n * n + j / n]); SudokuBoard[i][j].SetCandidate(c); } } } } /// <summary> /// 求解开始时寻找并填入单元格唯一可填的数,减少解空间 /// </summary> /// <returns>是否填入过数字,如果为false,表示能立即确定待填数字的单元格已经没有,要开始暴力搜索了</returns> private bool FillUniqueNumber(ref PathTree path) { var filled = false; for (int i = 0; i < SudokuBoard.Length; i++) { for (int j = 0; j < SudokuBoard[i].Length; j++) { if (!SudokuBoard[i][j].IsCondition && !SudokuBoard[i][j].IsUnique) { var canFillCount = 0; var index = -1; for (int k = 0; k < SudokuBoard[i][j].Candidate.Length; k++) { if (SudokuBoard[i][j].Candidate[k]) { index = k; canFillCount++; } if (canFillCount > 1) { break; } } if (canFillCount == 0) { throw new Exception("有单元格无法填入任何数字,数独无解"); } if (canFillCount == 1) { var num = (byte)(index + 1); SudokuBoard[i][j].SetNumber(num); SudokuBoard[i][j].SetUnique(); filled = true; var upRes = UpdateCandidate(i, j, num); if (!upRes.canFill) { throw new Exception("有单元格无法填入任何数字,数独无解"); } path = new PathTree(SudokuBoard[i][j], i, j, num, path); path.SetList = upRes.setList; } } } } return filled; } /// <summary> /// 更新单元格所在行、列、宫的其它单元格能填的数字候选,如果没有,会撤销更新 /// </summary> /// <param name="row">行号</param> /// <param name="column">列号</param> /// <param name="canNotFillNumber">要剔除的候选数字</param> /// <returns>更新候选数后,所有被更新的单元格是否都有可填的候选数字</returns> private (bool canFill, (byte x, byte y)[] setList) UpdateCandidate(int row, int column, byte canNotFillNumber) { var canFill = true; var list = new List<SudokuBlock>(); // 记录修改过的单元格,方便撤回修改 bool CanFillNumber(int i, int j) { var re = true; var _canFill = false; for (int k = 0; k < SudokuBoard[i][j].Candidate.Length; k++) { if (SudokuBoard[i][j].Candidate[k]) { _canFill = true; break; } } if (!_canFill) { re = false; } return re; } bool Update(int i, int j) { if (!(i == row && j == column) && !SudokuBoard[i][j].IsCondition && !SudokuBoard[i][j].IsUnique && SudokuBoard[i][j].Candidate[canNotFillNumber - 1]) { SudokuBoard[i][j].Candidate.Set(canNotFillNumber - 1, false); list.Add(SudokuBoard[i][j]); return CanFillNumber(i, j); } else { return true; } } //更新该行其余列 for (int j = 0; j < SudokuBoard[row].Length; j++) { canFill = Update(row, j); if (!canFill) { break; } } if (canFill) //只在行更新时没发现无数可填的单元格时进行列更新才有意义 { //更新该列其余行 for (int i = 0; i < SudokuBoard.Length; i++) { canFill = Update(i, column); if (!canFill) { break; } } } if (canFill)//只在行、列更新时都没发现无数可填的单元格时进行宫更新才有意义 { //更新该宫其余格 //n表示每宫应有的行、列数(标准数独行列、数相同) var n = (int)Sqrt(SudokuBoard.Length); //g为宫的编号,根据行号列号计算 var g = row / n * n + column / n; for (int i = g / n * n; i < g / n * n + n; i++) { for (int j = g % n * n; j < g % n * n + n; j++) { canFill = Update(i, j); if (!canFill) { goto canNotFill; } } } canNotFill:; } //如果发现存在没有任何数字可填的单元格,撤回所有候选修改 if (!canFill) { foreach (var cell in list) { cell.Candidate.Set(canNotFillNumber - 1, true); } } return (canFill, list.Select(x => (x.X, x.Y)).ToArray()); } /// <summary> /// 寻找下一个要尝试填数的格 /// </summary> /// <param name="i">起始行号</param> /// <param name="j">起始列号</param> /// <returns>找到的下一个行列号,没有找到返回-1</returns> private (int x, int y) NextBlock(int i = 0, int j = 0) { for (; i < SudokuBoard.Length; i++) { for (; j < SudokuBoard[i].Length; j++) { if (!SudokuBoard[i][j].IsCondition && !SudokuBoard[i][j].IsUnique && !SudokuBoard[i][j].Number.HasValue) { return (i, j); } } j = 0; } return (-1, -1); } 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])}"; } }
大多数都有注释,配合注释应该不难理解,如有问题欢迎评论区交流。稍微说一下,重载ToString是为了方便调试和查看状态,其中空心方框表示未填写数字的单元格,数字表示题目给出数字的单元格,圈数字表示唯一数单元格填写的数字,括号数字表示有多个候选数通过尝试(暴力搜索)确定的数字。注意类文件最上面有一个using static System.Math; 导入静态类,不然每次调用数学函数都要 Math. ,很烦。
求解过程信息
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; } }
其中记录了每个步骤在哪个单元格填写了哪个数字,上一步是哪一步,之后尝试过哪些步骤,这一步是否会导致之后的步骤出现死路,填写数字后影响到的单元格和候选数字(用来在悔步的时候恢复相应单元格的候选数字)。
答案存储
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])}"; } }
没什么好说的,就是保存答案的,因为有些数独的解不唯一,将来有机会扩展求多解时避免相互覆盖。
还有一个辅助类,单元格定义
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; } }
测试代码
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#中怎么实现一个数独求解算法问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。