C#中怎么实现一个数独求解算法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
成都创新互联公司坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站制作、网站设计、
外贸网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的
掇刀网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
1、先寻找并填写那些数单元格。在部分数独中有些单元格会因为同行、列、宫内题目已知数的限制,实际只有一个数可以填,这种单元格就应该趁早填好,因为没有尝试的必要,不提前处理掉还会影响之后求解的效率。在填写数字后,同行、列、宫的候选数就会减少,可能会出现新的数单元格,那么继续填写,直到没有数单元格为止。
2、检查是否已经完成游戏,也就是所有单元格都有数字。部分简单数独一直填数单元格就可以完成游戏。
3、按照单元格从左到右、从上到下,数字从小到大的顺序尝试填写有多个候选数的单元格,直到全部填完或者发现有单元格候选数为空。如果出现无候选数的单元格说明之前填错数导致出现死路,就需要悔步清除上一个单元格填过的数,换成下一个候选数继续尝试。如果清除后发现没有更大的候选数可填,说明更早之前就已经填错了,要继续悔步并换下一个候选数。有可能需要连续悔多步,一直悔步直到有更大的候选数可填的单元格。如果一路到最开始的单元格都没法填,说明这个数独有问题,无解。
代码(包括数独求解器,求解过程信息,答案存储三个主要类):
数独求解器
public class SudokuSolver { /// /// 题目面板 /// 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); } } } /// /// 求解数独 /// /// 获得的解 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; } } } /// /// 初始化候选项 /// private void InitCandidate() { //初始化每行空缺待填的数字 var rb = new List(); 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(); 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(); //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); } } } } /// /// 求解开始时寻找并填入单元格可填的数,减少解空间 /// /// 是否填入过数字,如果为false,表示能立即确定待填数字的单元格已经没有,要开始暴力搜索了 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; } /// /// 更新单元格所在行、列、宫的其它单元格能填的数字候选,如果没有,会撤销更新 /// /// 行号 /// 列号 /// 要剔除的候选数字 /// 更新候选数后,所有被更新的单元格是否都有可填的候选数字 private (bool canFill, (byte x, byte y)[] setList) UpdateCandidate(int row, int column, byte canNotFillNumber) { var canFill = true; var list = new List(); // 记录修改过的单元格,方便撤回修改 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()); } /// /// 寻找下一个要尝试填数的格 /// /// 起始行号 /// 起始列号 /// 找到的下一个行列号,没有找到返回-1 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 Children { get; } = new List(); 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 { /// /// 填入的数字 /// public byte? Number { get; private set; } /// /// X坐标 /// public byte X { get; } /// /// Y坐标 /// public byte Y { get; } /// /// 候选数字,下标所示状态表示数字“下标加1”是否能填入 /// public BitArray Candidate { get; private set; } /// /// 是否为条件(题目)给出数字的单元格 /// public bool IsCondition { get; } /// /// 是否为游戏开始就能确定可填数字的单元格 /// 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 GetPath(PathTree pathTree) { List list = new List(); var path = pathTree; while (path.Parent != null) { list.Add(path); path = path.Parent; } return list.Reverse(); } 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#中怎么实现一个数独求解算法问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。
网页标题:C#中怎么实现一个数独求解算法-创新互联
网站URL:
http://cdweb.net/article/csjhog.html