스도쿠 문제 (백준2580번)

개요

백준 단계별로 풀어보기 ➡️ 백트래킹 ➡️ 스도쿠 문제입니다.

사실 스도쿠게임에 관심이 없어 한번도 해 본적이 없었는데 문제를 풀다보니 어렵지만 재미있는 게임이라는 생각이 듭니다. 🤔

스도쿠 예시 (출처 : 위키백과)
게임 규칙은,

1. 아홉 3×3 칸에 숫자가 1부터 9까지 하나씩만 들어가야 한다.

2. 아홉 가로줄에 숫자가 1부터 9까지 하나씩만 들어가야 한다.

3. 아홉 세로줄에 숫자가 1부터 9까지 하나씩만 들어가야 한다.

 

예시의 정답은,

스도쿠 정답 (출처 : 위키백과)

위에 소개된 위키백과 스도쿠문제는 빈칸이 많아 난이도가 너무 높다는 생각이듭니다. 😥

아래 경우를 한번 살펴볼까요.

백준 스도쿠 예제

훨씬 직관적이고 이해하기 좋습니다. 😀

예를 들면 맨 왼쪽 상단 빈칸은 가로줄, 세로줄, 3x3 모두 숫자1이 없으므로 빈칸을 1로 채우고 진행이 가능해 보입니다.

이렇게 진행해 가다 문제가 생기면 다시 돌아와 다른 숫자를 넣어보면 문제를 해결할 수 있을 것 같은 느낌이 듭니다.

백준 스도쿠 정답

재귀호출과 백트래킹(Back-tracking) 알고리즘을 활용해 풀어보았습니다.

문제에 대한 설명은 아래 링크를 참조 바랍니다.

백준2580 스도쿠 문제

 

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
using namespace std;
 
const int SIZE = 9;
 
bool isOk(int arr[][SIZE], int r, int c, int n)
{
    // 행, 열에 해당 숫자체크
    for(int i=0; i<SIZE; i++)
    {
        if(arr[i][c]==n || arr[r][i]==n)
            return false;
    }
     
    // 3x3 숫자체크
    int x = (c/3)*3;
    int y = (r/3)*3;
    for(int i=0; i<3; i++)
    {
        for(int j=0; j<3; j++)
        {
            if(arr[i+y][j+x]==n)
                return false;
        }
    }
    return true;
}
 
bool sudoku(int arr[][SIZE])
{
    for(int i=0; i<SIZE; i++)
    {
        for(int j=0; j<SIZE; j++)
        {
            // 빈칸(0)이면, 1~9 넣어보기
            if(arr[i][j]==0)
            {
                for(int k=1; k<=9; k++)
                {
                    if(isOk(arr, i, j, k))
                    {
                        arr[i][j] = k;
                         
                        if(sudoku(arr)==true)
                            return true;
                         
                        // 실패시 되돌리기(백트래킹)
                        arr[i][j] = 0;
                    }
                }
                // 1~9 모두 실패
                return false;
            }
        }
    }
    return true;
}
 
int main()
{
    // 입력처리
    int arr[SIZE][SIZE];
    for(int i=0; i<SIZE; i++)
    {
        for(int j=0; j<SIZE; j++)
        {
            cin >> arr[i][j];
        }
    }
     
    if(sudoku(arr) == true)
    {
        //출력하기
        for(int i=0; i<SIZE; i++)
        {
            for(int j=0; j<SIZE; j++)
            {
                cout << arr[i][j] << ' ';
            }
            cout << '\n';
        }
    }
    return 0;
}

 

앞서 소개한 백준문제를 대입한 결과는 다음과 같습니다.

코드 실행결과

 

코드 설명

스도쿠를 풀기 위해 백트래킹(Back-tracking) 알고리즘 사용.

백트래킹이란?

가능한 모든 경우를 시도하면서 정답을 찾되, 

"이 길은 틀렸어!"라고 판단되면 이전으로 돌아가 다른 길을 시도하는 방법.

즉, 재귀 함수를 이용해서 숫자를 하나씩 넣어보고, 안 되면 되돌리는 방식으로 문제를 해결.

 

전체적인 코드의 구조는 3개의 함수로 구성.

1. isOk(int arr[][SIZE], int r, int c, int n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool isOk(int arr[][SIZE], int r, int c, int n)
{
    // 행, 열에 해당 숫자체크
    for(int i=0; i<SIZE; i++)
    {
        if(arr[i][c]==n || arr[r][i]==n)
            return false;
    }
 
    // 3x3 숫자체크
    int x = (c/3)*3;
    int y = (r/3)*3;
    for(int i=0; i<3; i++)
    {
        for(int j=0; j<3; j++)
        {
            if(arr[i+y][j+x]==n)
                return false;
        }
    }
    return true;
}

n을 (r, c) 위치에 넣을 수 있는지 체크하는 함수.

- 행과 열에서 숫자가 중복되는지 확인.

- 3x3 격자에서 숫자가 중복되는지 확인.

만약 숫자가 중복되지 않는다면 true, 중복된다면 false를 반환.

 

2. sudoku()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
bool sudoku(int arr[][SIZE])
{
    for(int i=0; i<SIZE; i++)
    {
        for(int j=0; j<SIZE; j++)
        {
            // 빈칸(0)이면, 1~9 넣어보기
            if(arr[i][j]==0)
            {
                for(int k=1; k<=9; k++)
                {
                    if(isOk(arr, i, j, k))
                    {
                        arr[i][j] = k;
 
                        if(sudoku(arr)==true)
                            return true;
 
                        // 실패시 되돌리기(백트래킹)
                        arr[i][j] = 0;
                    }
                }
                // 1~9 모두 실패
                return false;
            }
        }
    }
    return true;
}

- 빈 칸(0)이 있는 위치를 탐색.

- 빈 칸(0) 이면 1부터 9까지 숫자를 하나씩 넣기.

- 만약 isOk() 함수의 리턴값이, 

true이면 (이 숫자가 해당 위치에 가능), 재귀 호출(sudoku(arr))을 통해 다음 칸으로.

false이면, 이전 숫자로 되돌리고 다시 시도.(백트래킹)

- 진행하며 모든 빈 칸이 채워지면 true를 반환, 정답 출력

 

3. main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main()
{
    // 입력처리
    int arr[SIZE][SIZE];
    for(int i=0; i<SIZE; i++)
    {
        for(int j=0; j<SIZE; j++)
        {
            cin >> arr[i][j];
        }
    }
 
    if(sudoku(arr) == true)
    {
        //출력하기
        for(int i=0; i<SIZE; i++)
        {
            for(int j=0; j<SIZE; j++)
            {
                cout << arr[i][j] << ' ';
            }
            cout << '\n';
        }
    }
    return 0;
}

- arr[9][9], 2차원 배열 입, 출력을 담당.

 

이상으로 모든 설명을 마칩니다. 감사합니다.

댓글

이 블로그의 인기 게시물

Qt Designer 설치하기

PyQt5 기반 동영상 플레이어앱 만들기