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
非递归回溯法是一种数独问题的有效策略,它避免了函数深度嵌套带来的效率低下的问题。在Java中,我们可以采用数组来表示数独的格子,并通过两层循环遍历每一行和每一列,检查当前位置是否可以填入合法数字。 以下是简单的Java代码实现: ```java import java.util.Arrays; public class SudokuSolver { private static final int SIZE = 9; private boolean[][] board; // 数独网格 public SudokuSolver(boolean[][] board) { this.board = board; } // 检查当前位置(行i, 列j)是否可以填入数字n private boolean isValid(int i, int j, int n) { // 检查行、列以及3x3宫格都不冲突 for (int k = 0; k < SIZE; k++) { if (board[i][k] == n || board[k][j] == n || board[i / 3 * 3 + k / 3 / 3][j / 3 * 3 + k % 3 / 3] == n) return false; } return true; } // 回溯填数过程 private void solve(int i, int j) { if (j == SIZE) { // 到达右边界,换行继续填下一个单元格 i++; j = 0; } if (i >= SIZE) { // 决完一行,返回上一层处理下一行 return; } // 尝试填入1到9的每个数字 for (int n = 1; n <= SIZE; n++) { if (isValid(i, j, n)) { // 如果合法,尝试填入并进入下一格 board[i][j] = n; solve(i, j + 1); // 递进处理下一个单元格 if (solve(i, j)) // 如果无法继续则回退到上一步 break; } } } // 主入口,开始求 public boolean solveSudoku() { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE && board[i][j] == 0; j++) { solve(i, j); if (!board[i][j]) // 如果还没找到,说明之前的填入有误,需要回溯 return false; } } return true; } // 示例 public static void main(String[] args) { boolean[][] sudokuBoard = { {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} }; SudokuSolver solver = new SudokuSolver(sudokuBoard); System.out.println(Arrays.deepToString(solver.solveSudoku() ? solver.board : "No solution found")); } } ``` 这个程序首先初始化一个数独矩阵,然后在主函数`main`中创建一个`SudokuSolver`实例,并尝试数独。如果找到决方案,它会打印出填充后的数独;若无,则显示"No solution found"。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值