C#循环解数独(非递归)
思路:(默认空白的地方是0,填入数据为1~9)
1.在空白的地方填入一个数字,我们从1开始填入,每填入一个数,我们进行冲突检测,如果没有冲突,下个空白处继续当前流程
(为了优化,我们可以提前判定哪些数据可以填,不用从1开始到9依次填充)
2.当遇到冲突,当前数据清0,回到空白状态,并返回到上一个填数据的地方,将数据+1 (原本填1,现在填2),当数据填到9后,还是冲突,再回退到上一个进行操作
3.冲突检测,行、列、3*3小格子中是否有重复数字,如果有表示冲突。
开始:
我们用List<List<int>> 表示9*9的数据。暂时用List<List<string>>来封装数据。
List<List<string>> dataList = new List<List<string>>(); dataList.Add(new[] { "5", "3", ".", ".", "7", ".", ".", ".", "." }.ToList()); dataList.Add(new[] { "6", ".", ".", "1", "9", "5", ".", ".", "." }.ToList()); dataList.Add(new[] { ".", "9", "8", ".", ".", ".", ".", "6", "." }.ToList()); dataList.Add(new[] { "8", ".", ".", ".", "6", ".", ".", ".", "3" }.ToList()); dataList.Add(new[] { "4", ".", ".", "8", ".", "3", ".", ".", "1" }.ToList()); dataList.Add(new[] { "7", ".", ".", ".", "2", ".", ".", ".", "6" }.ToList()); dataList.Add(new[] { ".", "6", ".", ".", ".", ".", "2", "8", "." }.ToList()); dataList.Add(new[] { ".", ".", ".", "4", "1", "9", ".", ".", "5" }.ToList()); dataList.Add(new[] { ".", ".", ".", ".", "8", ".", ".", "7", "9" }.ToList());
其中"." 表示空白地方
上面的数据,进行处理后,可以转为List<List<int>>类型
List<List<int>> data = new List<List<int>>(); foreach (List<string> list in dataList) { data.Add(list.Select(it => Convert.ToInt32(it == "." ? "0" : it)).ToList()); }
首先我们把所有的要填数据的地方记录下来,可以用链表的方式实现,方便找到上一个空白点以及下一个空白点,方便实现回退
1.定义结构,用于存放行列,以及哪些数据可以填写
class Node { public int i; public int j; public List<int> CanSet; public Node front; public Node next; }
2.写个方法,实现某个空白位置,可以填写哪些数据(传入行列)
private List<int> GetCanSet(int a, int b) { //把当前行的所有数据记下来,把当前列的也记下来 List<int> list = new List<int>(); //把当前行的数据取出来 for (int j = 0; j < 9; j++) { if (data[a][j] != 0 && !list.Contains(data[a][j])) list.Add(data[a][j]); } //把当前列的数据取出来 for (int i = 0; i < 9; i++) { if (data[i][b] != 0 && !list.Contains(data[i][b])) list.Add(data[i][b]); } //把当前3*3格子内的数据取出来 int x = a / 3 * 3; int y = b / 3 * 3; for (int i = x; i < x + 3; i++) { for (int j = y; j < y + 3; j++) { if (data[i][j] != 0 && !list.Contains(data[i][j])) list.Add(data[i][j]); } } List<int> returnValue = new List<int>(); for (int i = 1; i <= 9; i++) { if (!list.Contains(i)) { returnValue.Add(i); } } return returnValue; }
3.初始化节点
bool isFirst = true; Node rootNode = null; Node lastNode = null; //将所有的要存放的数据记录一下,以及他的下一级是多少 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (data[i][j] == 0) { if (isFirst) { Node n = new Node { i = i, j = j }; n.CanSet = GetCanSet(i, j); rootNode = n; lastNode = n; isFirst = false; } else { Node n = new Node { i = i, j = j }; n.CanSet = GetCanSet(i, j); n.front = lastNode; lastNode.next = n; lastNode = n; } } } }
4.实现一个方法,用于冲突判断
/// <summary> /// 行列校验是否冲突 /// </summary> /// <param name="node"></param> /// <returns></returns> private bool CheckNode(Node node) { int value = data[node.i][node.j]; //当前行校验 for (int j = 0; j < 9; j++) { if (node.j == j) { continue; } if (data[node.i][j] == value) { return false; } } //当前列校验 for (int i = 0; i < 9; i++) { if (node.i == i) continue; if (data[i][node.j] == value) return false; } //校验小格子 int x = node.i / 3 * 3; int y = node.j / 3 * 3; for (int i = x; i < (x + 3); i++) { for (int j = y; j < (y + 3); j++) { if (node.i == i && node.j == j) continue; if (data[i][j] == value) return false; } } //校验通过 return true; }
5.循环每个空白节点填入数据
Node node = rootNode; //找到根节点,从第一个空位置开始填数据 while (true) //这里写死循环,下面有退出条件 { //当前节点为空,两种情况1.最后一个填完 2.已经回退到第一个节点 //这两种情况,表示填数据结束,退出循环 if (node == null) break; int value = data[node.i][node.j]; //得到当前节点原本的数据 int indexof = node.CanSet.IndexOf(value);//找到原来的数据在可放数据的哪个位置 indexof++; //索引位置+1 //如果数字原来就已经是可选范围的最后一个值,那么就应该回退 if (indexof == node.CanSet.Count) { data[node.i][node.j] = 0; node = node.front;//回退上一个节点 continue; } value = node.CanSet[indexof]; data[node.i][node.j] = value; if (CheckNode(node)) //冲突检测 { node = node.next;//没有冲突,就再下一个位置填数据 continue; } }
完整代码:
internal class Program { static void Main(string[] args) { List<List<string>> data = new List<List<string>>(); data.Add(new[] { "5", "3", ".", ".", "7", ".", ".", ".", "." }.ToList()); data.Add(new[] { "6", ".", ".", "1", "9", "5", ".", ".", "." }.ToList()); data.Add(new[] { ".", "9", "8", ".", ".", ".", ".", "6", "." }.ToList()); data.Add(new[] { "8", ".", ".", ".", "6", ".", ".", ".", "3" }.ToList()); data.Add(new[] { "4", ".", ".", "8", ".", "3", ".", ".", "1" }.ToList()); data.Add(new[] { "7", ".", ".", ".", "2", ".", ".", ".", "6" }.ToList()); data.Add(new[] { ".", "6", ".", ".", ".", ".", "2", "8", "." }.ToList()); data.Add(new[] { ".", ".", ".", "4", "1", "9", ".", ".", "5" }.ToList()); data.Add(new[] { ".", ".", ".", ".", "8", ".", ".", "7", "9" }.ToList()); Sudoku sudoku = new Sudoku(data); foreach (List<string> list in sudoku.Result) { Console.WriteLine(string.Join(",", list)); } sudoku.Solve(); Console.WriteLine("-----------------"); foreach (List<string> list in sudoku.Result) { Console.WriteLine(string.Join(",", list)); } Console.ReadLine(); } }
public class Sudoku { List<List<int>> data; public Sudoku(List<List<string>> data) { List<List<int>> data1 = new List<List<int>>(); foreach (List<string> list in data) { data1.Add(list.Select(it => Convert.ToInt32(it == "." ? "0" : it)).ToList()); } this.data = data1; } public List<List<string>> Result { get { List<List<string>> result = new List<List<string>>(); foreach (List<int> list in data) { result.Add(list.Select(it => Convert.ToString(it)).ToList()); } return result; } } public void Solve() { bool isFirst = true; Node rootNode = null; Node lastNode = null; //将所有的要存放的数据记录一下,以及他的下一级是多少 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (data[i][j] == 0) { if (isFirst) { Node n = new Node { i = i, j = j }; n.CanSet = GetCanSet(i, j); rootNode = n; lastNode = n; isFirst = false; } else { Node n = new Node { i = i, j = j }; n.CanSet = GetCanSet(i, j); n.front = lastNode; lastNode.next = n; lastNode = n; } } } } Node node = rootNode; while (true) { if (node == null) break; int value = data[node.i][node.j]; int indexof = node.CanSet.IndexOf(value); indexof++; //如果数字原来就已经是可选范围的最后一个值,那么就应该回退 if (indexof == node.CanSet.Count) { data[node.i][node.j] = 0; node = node.front; continue; } value = node.CanSet[indexof]; data[node.i][node.j] = value; if (CheckNode(node)) { node = node.next; continue; } } } private List<int> GetCanSet(int a, int b) { //把当前行的所有数据记下来,把当前列的也记下来 List<int> list = new List<int>(); //把当前行的数据取出来 for (int j = 0; j < 9; j++) { if (data[a][j] != 0 && !list.Contains(data[a][j])) list.Add(data[a][j]); } //把当前列的数据取出来 for (int i = 0; i < 9; i++) { if (data[i][b] != 0 && !list.Contains(data[i][b])) list.Add(data[i][b]); } //把当前3*3格子内的数据取出来 int x = a / 3 * 3; int y = b / 3 * 3; for (int i = x; i < x + 3; i++) { for (int j = y; j < y + 3; j++) { if (data[i][j] != 0 && !list.Contains(data[i][j])) list.Add(data[i][j]); } } List<int> returnValue = new List<int>(); for (int i = 1; i <= 9; i++) { if (!list.Contains(i)) { returnValue.Add(i); } } return returnValue; } /// <summary> /// 行列校验是否冲突 /// </summary> /// <param name="node"></param> /// <returns></returns> private bool CheckNode(Node node) { int value = data[node.i][node.j]; //当前行校验 for (int j = 0; j < 9; j++) { if (node.j == j) { continue; } if (data[node.i][j] == value) { return false; } } //当前列校验 for (int i = 0; i < 9; i++) { if (node.i == i) continue; if (data[i][node.j] == value) return false; } //校验小格子 int x = node.i / 3 * 3; int y = node.j / 3 * 3; for (int i = x; i < (x + 3); i++) { for (int j = y; j < (y + 3); j++) { if (node.i == i && node.j == j) continue; if (data[i][j] == value) return false; } } //校验通过 return true; } } class Node { public int i; public int j; public List<int> CanSet; public Node front; public Node next; }
拓展:
如果所有的都是空的呢?那按照之前的规则,每次运行出来的结果是不是都是一样的呢?
我们只需要将可填写的值随机打乱就行,那么每次运行,出来的结果就是不一样的。
private List<int> ListRandom(List<int> myList) { Random ran = new Random(); int index = 0; int temp = 0; for (int i = 0; i < myList.Count; i++) { index = ran.Next(0, myList.Count - 1); if (index != i) { temp = myList[i]; myList[i] = myList[index]; myList[index] = temp; } } return myList; }
改写
private List<int> GetCanSet(int a, int b) { //把当前行的所有数据记下来,把当前列的也记下来 List<int> list = new List<int>(); //把当前行的数据取出来 for (int j = 0; j < 9; j++) { if (data[a][j] != 0 && !list.Contains(data[a][j])) list.Add(data[a][j]); } //把当前列的数据取出来 for (int i = 0; i < 9; i++) { if (data[i][b] != 0 && !list.Contains(data[i][b])) list.Add(data[i][b]); } //把当前3*3格子内的数据取出来 int x = a / 3 * 3; int y = b / 3 * 3; for (int i = x; i < x + 3; i++) { for (int j = y; j < y + 3; j++) { if (data[i][j] != 0 && !list.Contains(data[i][j])) list.Add(data[i][j]); } } List<int> returnValue = new List<int>(); for (int i = 1; i <= 9; i++) { if (!list.Contains(i)) { returnValue.Add(i); } } return ListRandom(returnValue); }