본문 바로가기

Algorithm/Baekjoon Oline Judge - Java

[백준] 2580_스토쿠 / 백트래킹, DFS

 

 

Gold

 

 

문제

 

스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다.

이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 일부 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다.

 

 

나머지 빈 칸을 채우는 방식은 다음과 같다.

  1. 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
  2. 굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.

위의 예의 경우, 첫째 줄에는 1을 제외한 나머지 2부터 9까지의 숫자들이 이미 나타나 있으므로 첫째 줄 빈칸에는 1이 들어가야 한다.

 

 

또한 위쪽 가운데 위치한 3x3 정사각형의 경우에는 3을 제외한 나머지 숫자들이 이미 쓰여있으므로 가운데 빈 칸에는 3이 들어가야 한다.

 

 

 

이와 같이 빈 칸을 차례로 채워 가면 다음과 같은 최종 결과를 얻을 수 있다.

 

 

게임 시작 전 스도쿠 판에 쓰여 있는 숫자들의 정보가 주어질 때 모든 빈 칸이 채워진 최종 모습을 출력하는 프로그램을 작성하시오.

 

 

입력

 

아홉 줄에 걸쳐 한 줄에 9개씩 게임 시작 전 스도쿠판 각 줄에 쓰여 있는 숫자가 한 칸씩 띄워서 차례로 주어진다.

스도쿠 판의 빈 칸의 경우에는 0이 주어진다.

스도쿠 판을 규칙대로 채울 수 없는 경우의 입력은 주어지지 않는다.

 

 

출력

 

모든 빈 칸이 채워진 스도쿠 판의 최종 모습을 아홉 줄에 걸쳐 한 줄에 9개씩 한 칸씩 띄워서 출력한다.

스도쿠 판을 채우는 방법이 여럿인 경우는 그 중 하나만을 출력한다.

 

 

 


 

풀이

 

import java.util.*;
import java.io.*;

class Main {
    static int[][] maps = new int[9][9];
    static List<int[]> list = new ArrayList<>();
    static boolean solve = false;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        for(int i=0; i<9; i++){
            st = new StringTokenizer(br.readLine());
            for(int j=0; j<9; j++){
                maps[i][j] = Integer.parseInt(st.nextToken());
                // 빈 칸의 경우만 list에 따로 좌표를 저장
                if(maps[i][j] == 0) list.add(new int[] {i, j});
            }
        }

        dfs(0);
        
        StringBuilder sb = new StringBuilder();
        
        for(int i=0; i<9; i++){
            for(int j=0; j<9; j++){
                sb.append(maps[i][j] + " ");
            }
            sb.append("\n");
        }
        
        System.out.println(sb);

    }

    static void dfs(int index){
		
        // 빈 칸을 모두 채운 경우 탐색 종료 (백트래킹)
        if(index == list.size()){
            solve = true;
            return;
        }

        int[] pos = list.get(index);
        int x = pos[0];
        int y = pos[1];
        
        for(int i=1; i<=9; i++){
            if(checkAll(x, y, i)){
                maps[x][y] = i;
                dfs(index+1);
                if (solve) return;  // 모든 빈칸을 채운 경우 종료
                maps[x][y] = 0;
            }
        }
    }

    static boolean checkAll(int row, int col, int val){
        boolean flag;
                 flag = checkCol(col, val); // 열 검사
        if(flag) flag = checkRow(row, val); // 행 검사
        if(flag) flag = checkBox(row, col, val); // 박스 검사

        return flag;
    }

	// 행 검사
    static boolean checkRow(int row, int val){
        for(int i=0; i<9; i++){
            if(maps[row][i]==val)
                return false;
        }
        
        return true;
    }
    
    // 열 검사
    static boolean checkCol(int col, int val){
        for(int i=0; i<9; i++){
            if(maps[i][col]==val)
                return false;
        }
        return true;
    }

	// 박스 검사
    static boolean checkBox(int row, int col, int val){
        int x = row/3*3;
        int y = col/3*3;
        for(int i=x; i<x+3; i++){
            for(int j=y; j<y+3; j++){
                if(maps[i][j]==val) return false;
            }
        }
        return true;
    }
    
}

 

 

 

해결방법

  1. 9x9 배열을 생성하여 스도쿠를 입력받아 저장한다. 
  2. 값이 0인 좌표만 따로 list에 저장해둔다
  3. DFS 탐색을 통해 스토쿠 빈칸을 채워나간다. 
    1. dfs(index) 함수를 통해 리스트를 순서대로 탐색한다. 
    2. 빈칸에 가능한 숫자를 하나씩 대입하고, 해당 값이 유효성을 검사한다
      -> checkRow, checkCol, checkBox 함수로 각각 행, 열, 박스 안에서 해당 숫자의 중복 여부를 검사한다. 
    3. 유효성이 검증되었을 경우 재귀적으로 다음 빈칸으로 이동한다
    4. 모든 빈칸을 채웠을 경우 (list의 모든 값을 탐색했을 경우) solve를 true로 설정해 탐색을 종료한다.

 

 

사용한 자료구조

  • DFS (깊이 우선 탐색): 빈칸을 재귀적으로 탐색할 수 있으며, DFS는 깊이를 먼저 탐색할 수 있다. 
    스토쿠는 단일 해를 찾는 문제이며, DFS는 해를 찾자마자 탐색을 종료할 수 있기 때문에 가능한 모든 탐색을 하는 BFS보다 효율적이다. 

 

 

 


https://www.acmicpc.net/problem/2580