C#中怎么實(shí)現(xiàn)一個(gè)數(shù)獨(dú)求解算法,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。
10年積累的網(wǎng)站制作、成都做網(wǎng)站經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先建設(shè)網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有平壩免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
1、先尋找并填寫那些唯一數(shù)單元格。在部分?jǐn)?shù)獨(dú)中有些單元格會(huì)因?yàn)橥?、列、宮內(nèi)題目已知數(shù)的限制,實(shí)際只有一個(gè)數(shù)可以填,這種單元格就應(yīng)該趁早填好,因?yàn)闆]有嘗試的必要,不提前處理掉還會(huì)影響之后求解的效率。在填寫數(shù)字后,同行、列、宮的候選數(shù)就會(huì)減少,可能會(huì)出現(xiàn)新的唯一數(shù)單元格,那么繼續(xù)填寫,直到?jīng)]有唯一數(shù)單元格為止。
2、檢查是否已經(jīng)完成游戲,也就是所有單元格都有數(shù)字。部分簡單數(shù)獨(dú)一直填唯一數(shù)單元格就可以完成游戲。
3、按照單元格從左到右、從上到下,數(shù)字從小到大的順序嘗試填寫有多個(gè)候選數(shù)的單元格,直到全部填完或者發(fā)現(xiàn)有單元格候選數(shù)為空。如果出現(xiàn)無候選數(shù)的單元格說明之前填錯(cuò)數(shù)導(dǎo)致出現(xiàn)死路,就需要悔步清除上一個(gè)單元格填過的數(shù),換成下一個(gè)候選數(shù)繼續(xù)嘗試。如果清除后發(fā)現(xiàn)沒有更大的候選數(shù)可填,說明更早之前就已經(jīng)填錯(cuò)了,要繼續(xù)悔步并換下一個(gè)候選數(shù)。有可能需要連續(xù)悔多步,一直悔步直到有更大的候選數(shù)可填的單元格。如果一路到最開始的單元格都沒法填,說明這個(gè)數(shù)獨(dú)有問題,無解。
代碼(包括數(shù)獨(dú)求解器,求解過程信息,答案存儲(chǔ)三個(gè)主要類):
數(shù)獨(dú)求解器
public class SudokuSolver { /// <summary> /// 題目面板 /// </summary> public SudokuBlock[][] SudokuBoard { get; } public SudokuSolver(byte[][] board) { SudokuBoard = new SudokuBlock[board.Length][]; //初始化數(shù)獨(dú)的行 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> /// 求解數(shù)獨(dú) /// </summary> /// <returns>獲得的解</returns> public IEnumerable<(SudokuState sudoku, PathTree path)> Solve(bool multiAnswer = false) { //初始化各個(gè)單元格能填入的數(shù)字 InitCandidate(); var pathRoot0 = new PathTree(null, -1, -1, -1); //填寫路徑樹,在非遞歸方法中用于記錄回退路徑和其他有用信息,初始生成一個(gè)根 var path0 = pathRoot0; //循環(huán)填入能填入的數(shù)字只有一個(gè)的單元格,每次填入都可能產(chǎn)生新的唯一數(shù)單元格,直到?jīng)]有唯一數(shù)單元格可填 while (true) { if (!FillUniqueNumber(ref path0)) { break; } } //檢查是否在填唯一數(shù)單元格時(shí)就已經(jīng)把所有單元格填滿了 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); //填寫路徑樹,在非遞歸方法中用于記錄回退路徑和其他有用信息,初始生成一個(gè)根 var path = pathRoot; var toRe = new List<(SudokuState sudoku, PathTree path)>(); //還存在需要試數(shù)才能求解的單元格,開始暴力搜索 int i = 0, j = 0; while (true) { (i, j) = NextBlock(i, j); //正常情況下返回-1表示已經(jīng)全部填完 if (i == -1 && j == -1 && !multiAnswer) { var pathLast = path;//記住最后一步 var path2 = path; while(path2.Parent.X != -1 && path2.Parent.Y != -1) { path2 = path2.Parent; } //將暴力搜索的第一步追加到唯一數(shù)單元格的填寫步驟的最后一步之后,連接成完整的填數(shù)步驟 path0.Children.Add(path2); path2.Parent = path0; yield return (new SudokuState(this), pathLast); break; } var numNode = path.Children.LastOrDefault(); //確定要從哪個(gè)數(shù)開始進(jìn)行填入嘗試 var num = numNode == null ? 0 : numNode.Number; bool filled = false; //是否發(fā)現(xiàn)可以填入的數(shù) //循環(huán)查看從num開始接下來的候選數(shù)是否能填(num是最后一次填入的數(shù),傳到Candidate[]的索引器中剛好指向 num + 1是否能填的存儲(chǔ)位,對(duì)于標(biāo)準(zhǔn)數(shù)獨(dú),候選數(shù)為 1~9,Candidate的索引范圍就是 0~8) for (; !SudokuBoard[i][j].IsCondition && !SudokuBoard[i][j].IsUnique && num < SudokuBoard[i][j].Candidate.Length; num++) { //如果有可以填的候選數(shù),理論上不會(huì)遇見沒有可以填的情況,這種死路情況已經(jīng)在UpdateCandidate時(shí)檢查了 if (SudokuBoard[i][j].Candidate[num] && !path.Children.Any(x => x.Number - 1 == num && !x.Pass)) { filled = true; //進(jìn)來了說明單元格有數(shù)可以填 //記錄步驟 var node = new PathTree(SudokuBoard[i][j], i, j, num + 1, path); path = node; //如果更新相關(guān)單元格的候選數(shù)時(shí)發(fā)現(xiàn)死路(更新函數(shù)會(huì)在發(fā)現(xiàn)死路時(shí)自動(dòng)撤銷更新) (bool canFill, (byte x, byte y)[] setList) updateResult = UpdateCandidate(i, j, (byte)(num + 1)); if (!updateResult.canFill) { //記錄這條路是死路 path.SetPass(false); } //僅在確認(rèn)是活路時(shí)設(shè)置填入數(shù)字 if (path.Pass) { SudokuBoard[i][j].SetNumber((byte)(num + 1)); path.SetList = updateResult.setList;//記錄相關(guān)單元格可填數(shù)更新記錄,方便在回退時(shí)撤銷更新 } else //出現(xiàn)死路,要進(jìn)行回退,重試這個(gè)單元格的其他可填數(shù)字 { path.Block.SetNumber(null); path = path.Parent; } //填入一個(gè)候選數(shù)后跳出循環(huán),不再繼續(xù)嘗試填入之后的候選數(shù) break; } } if (!filled)//如果沒有成功填入數(shù)字,說明上一步填入的單元格就是錯(cuò)的,會(huì)導(dǎo)致后面的單元格怎么填都不對(duì),要回退到上一個(gè)單元格重新填 { 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> /// 初始化候選項(xiàng) /// </summary> private void InitCandidate() { //初始化每行空缺待填的數(shù)字 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列是條件(題目)給出的數(shù),設(shè)置數(shù)字不能再填(r[x] == false 表示 i 行不能再填 x + 1,下標(biāo)加1表示數(shù)獨(dú)可用的數(shù)字,下標(biāo)對(duì)應(yīng)的值表示下標(biāo)加1所表示的數(shù)是否還能填入該行) if (SudokuBoard[i][j].IsCondition || SudokuBoard[i][j].IsUnique) { r.Set(SudokuBoard[i][j].Number.Value - 1, false); } } rb.Add(r); } //初始化每列空缺待填的數(shù)字 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); } //初始化每宮空缺待填的數(shù)字(目前只能算標(biāo)準(zhǔn) n×n 數(shù)獨(dú)的宮) var gb = new List<BitArray>(); //n表示每宮應(yīng)有的行、列數(shù)(標(biāo)準(zhǔn)數(shù)獨(dú)行列、數(shù)相同) 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); } //初始化每格可填的候選數(shù)字 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); //當(dāng)前格能填的數(shù)為其所在行、列、宮同時(shí)空缺待填的數(shù)字,按位與運(yùn)算后只有同時(shí)能填的候選數(shù)保持1(可填如當(dāng)前格),否則變成0 // i / n * n + j / n:根據(jù)行號(hào)列號(hào)計(jì)算宮號(hào), c = c.And(rb[i]).And(cb[j]).And(gb[i / n * n + j / n]); SudokuBoard[i][j].SetCandidate(c); } } } } /// <summary> /// 求解開始時(shí)尋找并填入單元格唯一可填的數(shù),減少解空間 /// </summary> /// <returns>是否填入過數(shù)字,如果為false,表示能立即確定待填數(shù)字的單元格已經(jīng)沒有,要開始暴力搜索了</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("有單元格無法填入任何數(shù)字,數(shù)獨(dú)無解"); } 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("有單元格無法填入任何數(shù)字,數(shù)獨(dú)無解"); } path = new PathTree(SudokuBoard[i][j], i, j, num, path); path.SetList = upRes.setList; } } } } return filled; } /// <summary> /// 更新單元格所在行、列、宮的其它單元格能填的數(shù)字候選,如果沒有,會(huì)撤銷更新 /// </summary> /// <param name="row">行號(hào)</param> /// <param name="column">列號(hào)</param> /// <param name="canNotFillNumber">要剔除的候選數(shù)字</param> /// <returns>更新候選數(shù)后,所有被更新的單元格是否都有可填的候選數(shù)字</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) //只在行更新時(shí)沒發(fā)現(xiàn)無數(shù)可填的單元格時(shí)進(jìn)行列更新才有意義 { //更新該列其余行 for (int i = 0; i < SudokuBoard.Length; i++) { canFill = Update(i, column); if (!canFill) { break; } } } if (canFill)//只在行、列更新時(shí)都沒發(fā)現(xiàn)無數(shù)可填的單元格時(shí)進(jìn)行宮更新才有意義 { //更新該宮其余格 //n表示每宮應(yīng)有的行、列數(shù)(標(biāo)準(zhǔn)數(shù)獨(dú)行列、數(shù)相同) var n = (int)Sqrt(SudokuBoard.Length); //g為宮的編號(hào),根據(jù)行號(hào)列號(hào)計(jì)算 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:; } //如果發(fā)現(xiàn)存在沒有任何數(shù)字可填的單元格,撤回所有候選修改 if (!canFill) { foreach (var cell in list) { cell.Candidate.Set(canNotFillNumber - 1, true); } } return (canFill, list.Select(x => (x.X, x.Y)).ToArray()); } /// <summary> /// 尋找下一個(gè)要嘗試填數(shù)的格 /// </summary> /// <param name="i">起始行號(hào)</param> /// <param name="j">起始列號(hào)</param> /// <returns>找到的下一個(gè)行列號(hào),沒有找到返回-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])}"; } }
大多數(shù)都有注釋,配合注釋應(yīng)該不難理解,如有問題歡迎評(píng)論區(qū)交流。稍微說一下,重載ToString是為了方便調(diào)試和查看狀態(tài),其中空心方框表示未填寫數(shù)字的單元格,數(shù)字表示題目給出數(shù)字的單元格,圈數(shù)字表示唯一數(shù)單元格填寫的數(shù)字,括號(hào)數(shù)字表示有多個(gè)候選數(shù)通過嘗試(暴力搜索)確定的數(shù)字。注意類文件最上面有一個(gè)using static System.Math; 導(dǎo)入靜態(tài)類,不然每次調(diào)用數(shù)學(xué)函數(shù)都要 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; } }
其中記錄了每個(gè)步驟在哪個(gè)單元格填寫了哪個(gè)數(shù)字,上一步是哪一步,之后嘗試過哪些步驟,這一步是否會(huì)導(dǎo)致之后的步驟出現(xiàn)死路,填寫數(shù)字后影響到的單元格和候選數(shù)字(用來在悔步的時(shí)候恢復(fù)相應(yīng)單元格的候選數(shù)字)。
答案存儲(chǔ)
public class SudokuState { public SudokuBlock[][] SudokuBoard { get; } public SudokuState(SudokuSolver sudoku) { SudokuBoard = new SudokuBlock[sudoku.SudokuBoard.Length][]; //初始化數(shù)獨(dú)的行 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])}"; } }
沒什么好說的,就是保存答案的,因?yàn)橛行?shù)獨(dú)的解不唯一,將來有機(jī)會(huì)擴(kuò)展求多解時(shí)避免相互覆蓋。
還有一個(gè)輔助類,單元格定義
public class SudokuBlock { /// <summary> /// 填入的數(shù)字 /// </summary> public byte? Number { get; private set; } /// <summary> /// X坐標(biāo) /// </summary> public byte X { get; } /// <summary> /// Y坐標(biāo) /// </summary> public byte Y { get; } /// <summary> /// 候選數(shù)字,下標(biāo)所示狀態(tài)表示數(shù)字“下標(biāo)加1”是否能填入 /// </summary> public BitArray Candidate { get; private set; } /// <summary> /// 是否為條件(題目)給出數(shù)字的單元格 /// </summary> public bool IsCondition { get; } /// <summary> /// 是否為游戲開始就能確定唯一可填數(shù)字的單元格 /// </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},}; //這個(gè)簡單,無需嘗試,一直填唯一數(shù)單元格,填完后剩下的單元格又有會(huì)變唯一數(shù)單元格 //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},}; //可以填一部分唯一數(shù)單元格,剩下一部分需要嘗試,調(diào)試用 //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(); }
關(guān)于C#中怎么實(shí)現(xiàn)一個(gè)數(shù)獨(dú)求解算法問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
當(dāng)前文章:C#中怎么實(shí)現(xiàn)一個(gè)數(shù)獨(dú)求解算法
分享路徑:http://jinyejixie.com/article14/igoede.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、App設(shè)計(jì)、企業(yè)建站、虛擬主機(jī)、自適應(yīng)網(wǎng)站、定制網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)