教你怎么用Java回溯算法解数独
作者:趋向_quxiang 发布时间:2021-12-14 02:27:18
标签:Java,回溯,算法,数独
一、题干
输入一个9*9二维数组表示数独,已经填入的数字用1-9表示,待填入的数字用0表示,试写一个算法解出数独并输出。
二、思路
容易想到回溯法,即以人的思维的解数独,遍历数组,如果是空白就从1-9依次选一个数判断本行、列、3*3宫格内是否有重复,如果有就进行下一个数字的选择;如果该数暂时满足条件,那么进行下一个格子的选择,递归的终止条件是遍历完所有格子。
三、代码分段演示
输入数组
Scanner sc = new Scanner(System.in);
int[][] board = new int[9][9];
// 输入
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
board[i][j] = sc.nextInt();
}
}
dfs回溯
(r, c)
是当前正在判断的格子坐标,board[r][c] == 0
判断这个格子是否还没有填数,如果没有,就从1-9依次选取一个数,先判断选的这个数是否合法,如果合法就做选择,并开始下一个格子的判断,决定完下一个格子之后就撤销选择(这是回溯法基本框架);如果该格子已填数,直接开始下一个格子的判断。终止条件就是r==9
,也就是二维数组遍历完。
public static void dfs(int[][] board, int r, int c) {
// 所有数填完了,输出
if (r == 9) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
System.out.print(board[i][j] + " ");
}
System.out.println();
}
return;
}
// 空白填数
if (board[r][c] == 0) {
// 从 1-9 中依次选数
for (int k = 1; k <= 9; k++) {
// 先判断放进去是否满足条件再选择
if (isValid(board, r, c, k)) {
// 做选择
board[r][c] = k;
// 决定下一个格子
next(board, r, c);
// 撤销选择
board[r][c] = 0;
}
}
}
// 非空白直接决定下一个格子
else next(board, r, c);
}
在二维数组中,下一个格子有两种可能:一是就在本行只需列+1即可,二是当前格子是本行最后一个,那么下一个格子就是下一行第一个。
// 递归下一个格子
public static void next(int[][] board, int r, int c) {
if (c + 1 == 9) dfs(board, r + 1, 0);
else dfs(board, r, c + 1);
}
判断是否满足条件
行和列的判断就不必细说了,关键是3*3宫格的判断,行 / 3 * 3 和 列 / 3 * 3 就是所在的3*3宫格左上角格子的坐标,其中 / 是地板除法
// 判断是否合法
public static boolean isValid(int[][] board, int r, int c, int val) {
// 列
for (int i = 0; i < 9; i++) {
if (board[i][c] == val) return false;
}
// 行
for (int j = 0; j < 9; j++) {
if (board[r][j] == val) return false;
}
// 3 * 3
for (int x = r / 3 * 3, i = x; i < x + 3; i++) {
for (int y = c / 3 * 3, j = y; j < y + 3; j++) {
if (board[i][j] == val) return false;
}
}
return true;
}
四、完整代码
public static void main(String[] args) {
solveSudoku();
}
public static void solveSudoku() {
Scanner sc = new Scanner(System.in);
int[][] board = new int[9][9];
// 输入
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
board[i][j] = sc.nextInt();
}
}
dfs(board, 0, 0);
}
// 回溯填数
public static void dfs(int[][] board, int r, int c) {
// 所有数填完了,输出
if (r == 9) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
System.out.print(board[i][j] + " ");
}
System.out.println();
}
return;
}
// 空白填数
if (board[r][c] == 0) {
for (int k = 1; k <= 9; k++) {
if (isValid(board, r, c, k)) {
// 做选择
board[r][c] = k;
// 决定下一个格子
next(board, r, c);
// 撤销选择
board[r][c] = 0;
}
}
}
// 非空白直接决定下一个格子
else next(board, r, c);
}
// 递归下一个格子
public static void next(int[][] board, int r, int c) {
if (c + 1 == 9) dfs(board, r + 1, 0);
else dfs(board, r, c + 1);
}
// 判断是否合法
public static boolean isValid(int[][] board, int r, int c, int val) {
// 列
for (int i = 0; i < 9; i++) {
if (board[i][c] == val) return false;
}
// 行
for (int j = 0; j < 9; j++) {
if (board[r][j] == val) return false;
}
// 3 * 3
for (int x = r / 3 * 3, i = x; i < x + 3; i++) {
for (int y = c / 3 * 3, j = y; j < y + 3; j++) {
if (board[i][j] == val) return false;
}
}
return true;
}
顺便提供几个示例输入输出
输入:
5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9
输出:
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9
输入:
0 0 0 5 0 0 9 0 0
6 0 4 2 0 3 0 0 0
5 0 0 0 6 0 0 3 2
0 0 0 0 9 0 4 0 0
4 2 0 1 0 5 0 7 9
0 0 9 7 0 0 1 0 0
9 0 0 0 0 6 0 1 8
2 0 0 0 4 0 0 0 5
0 0 0 0 0 0 6 0 0
输出:
7 3 2 5 8 1 9 6 4
6 9 4 2 7 3 5 8 1
5 1 8 4 6 9 7 3 2
1 7 5 6 9 8 4 2 3
4 2 6 1 3 5 8 7 9
3 8 9 7 2 4 1 5 6
9 4 7 3 5 6 2 1 8
2 6 1 8 4 7 3 9 5
8 5 3 9 1 2 6 4 7
输入:
0 0 9 7 4 8 0 0 0
7 0 0 0 0 0 0 0 0
0 2 0 1 0 9 0 0 0
0 0 7 0 0 0 2 4 0
0 6 4 0 1 0 5 9 0
0 9 8 0 0 0 3 0 0
0 0 0 8 0 3 0 2 0
0 0 0 0 0 0 0 0 6
0 0 0 2 7 5 9 0 0
输出:
5 1 9 7 4 8 6 3 2
7 8 3 6 5 2 4 1 9
4 2 6 1 3 9 8 7 5
3 5 7 9 8 6 2 4 1
2 6 4 3 1 7 5 9 8
1 9 8 5 2 4 3 6 7
9 7 5 8 6 3 1 2 4
8 3 2 4 9 1 7 5 6
6 4 1 2 7 5 9 8 3
来源:https://blog.csdn.net/qq_45822970/article/details/117414590


猜你喜欢
- SpringBoot的web项目,在每一次修改了java文件或者是resource的时候,都必须去重启一下项目,这样的话浪费了很多的时间,实
- 1、Java字符串在 Java 中字符串被作为 String 类型的对象处理。 String 类位于 java.lang 包中,默认情况下该
- PUT和Delete请求使用在Form表单中,只支持get和post方式,而为了实现put方式我们可以通过如下三个步骤实现1)SpringM
- 简介SpringBoot提供了HATEOAS的便捷使用方式,本文详细讲解SpringBoot提供的这些基本方法。链接LinksHATEOAS
- 目录一、背景二、推荐方式2.1 自定义的枚举2.2 外部枚举三、总结一、背景平时工作开发过程中,难免会用到状态机(状态的流转)。如奖学金审批
- 使用Task类解决线程的等待问题在任何的编程语言中,面对耗时任务时,我们都会有这样的需求:让任务执行一定时间,主任务进行等待,如果到时仍然完
- 简介区别String的缺点是每次字符串变量的内容发生了改变时,都必须重新分配内存。你想想,如果创建一个迭代100000次的循环,每次迭代都将
- 问题使用前后端分离模式开发项目时,往往会遇到这样一个问题 -- 无法跨域获取服务端数据这是由于浏览器的同源策略导致的,目的是为了安全。在前后
- listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明。
- 本文实例讲述了C#将Json解析成DateTable的方法。分享给大家供大家参考。具体实现方法如下:#region 将 Json 解析成 D
- Spring的HandlerMapping支持 * , * 必须实现HandlerInterceptor接口,此接口里面有下面3中方法:1.
- 本文实例讲述了C#实现系统托盘通知的方法。分享给大家供大家参考。具体实现方法如下:namespace WindowsApplication1
- 短网址,忽然一下子就冒出来的东西,长长的一个URL,提交过去,出来就只有短短的一个URL了,看起来似乎挺神奇,其实简单分析一下,明白其中的原
- Java内置GUI Frame类Frame概述* 事件处理 * 事件: 用户的一个操作* 事件源: * 作的组件*
- jdk线程池ThreadPoolExecutor的7个参数public ThreadPoolExecutor(int corePoolSiz
- SQL 映射XML 文件是所有sql语句放置的地方。需要定义一个workspace,一般定义为对应的接口类的路径。写好SQL语句映射文件后,
- 本文研究的主要是优化MyBatis配置文件中的配置的相关内容,具体介绍如下。一、连接数据库的配置单独放在一个properties文件中之前,
- 当我们在做前后端分离的开发时,在使用fetch交换数据的时候,提示Access-Control-Allow-Origin跨域问题,解决方案跟
- 在算法面试中,面试官总是喜欢围绕链表、排序、二叉树、二分查找来做文章,而大多数人都可以跟着专业的书籍来做到倒背如流。而面试官并不希望招收的是
- 随着时间的推移现在的软件要求显示的内容越来越多,所以要在小的屏幕上能够更好的显示更多的内容,首先我们会想到底部菜单栏,但是有时候像今日头条新