https://www.acmicpc.net/problem/2580
스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 일부 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다
나머지 빈 칸을 채우는 방식은 다음과 같다.
각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
위의 예의 경우, 첫째 줄에는 1을 제외한 나머지 2부터 9까지의 숫자들이 이미 나타나 있으므로 첫째 줄 빈칸에는 1이 들어가야 한다.또한 위쪽 가운데 위치한 3x3 정사각형의 경우에는 3을 제외한 나머지 숫자들이 이미 쓰여있으므로 가운데 빈 칸에는 3이 들어가야 한다.
이와 같이 빈 칸을 차례로 채워 가면 다음과 같은 최종 결과를 얻을 수 있다.
게임 시작 전 스도쿠 판에 쓰여 있는 숫자들의 정보가 주어질 때 모든 빈 칸이 채워진 최종 모습을 출력하는 프로그램을 작성하시오.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int[][] sdoku;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
sdoku = new int[9][9];
for (int r = 0; r < 9; r++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int c = 0; c < 9; c++) {
sdoku[r][c] = Integer.parseInt(st.nextToken());
}
} // 입력
start(0, 0); // 스도쿠 풀기
StringBuilder sb = new StringBuilder();
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 9; c++) {
sb.append(sdoku[r][c]).append(" ");
}
sb.append("\n");
}
System.out.println(sb);
}
private static boolean start(int R, int C) {
if (C == 9) {
// 열이 끝나면
return start(R+1, 0); // 다음줄로 다시 시작
}
if (R == 9)
return true;
if(sdoku[R][C] != 0) return start(R, C+1);
for (int i = 1; i <= 9; i++) {
if (check(R, C, i)) {
sdoku[R][C] = i;
if(start(R, C+1)) {
return true;
};
sdoku[R][C] = 0;
}
}
return false;
}
private static boolean check(int R, int C, int num) {
// 가로 검사
for (int c = 0; c < 9; c++) {
if (sdoku[R][c] == num) {
return false;
}
}
// 세로 검사
for (int r = 0; r < 9; r++) {
if (sdoku[r][C] == num) {
return false;
}
}
// 정사각 검사
int nR = R / 3 * 3;
int nC = C / 3 * 3;
for (int r = nR; r < nR + 3; r++) {
for (int c = nC; c < nC + 3; c++) {
if (sdoku[r][c] == num) {
return false;
}
}
}
return true;
}
}
현재 상태에서 다음 상태로 가는 모든 경우의 수를 찾아 모든 경우의 수가 유망하지 않다고 판단되면, 이전 상태로 돌아간다. 탐색할 필요가 없는 상태를 제외하는 것을 가지치기라고 부른다.
해당 알고리즘을 잘 사용하면 최적화를 잘 할 수 있다. 현재 스도쿠 문제를 정말 스도쿠 풀듯이 풀게 되면, 시간 초과가 발생하기 때문에, 조건문을 잘 사용하여 가지치기를 효율적으로 해주면 좋다.
private static boolean start(int R, int C) {
if (C == 9) {
// 열이 끝나면
return start(R+1, 0); // 다음줄로 다시 시작
}
if (R == 9)
return true;
if(sdoku[R][C] != 0) return start(R, C+1);
for (int i = 1; i <= 9; i++) {
if (check(R, C, i)) {
sdoku[R][C] = i;
if(start(R, C+1)) {
return true;
};
sdoku[R][C] = 0;
}
}
return false;
}
해당 풀이에서 if(start(R,C+1)) return true; 해당 부분이 없다면, 모든 경우의 수를 다 돌고 나오는 것이다.