[알고리즘] 백트래킹(Backtracking) | C언어 N-Queen구현

백트래킹이란?

  • 백트래킹(Backtracking) 은 해를 찾는 도중 해가 아니어서 막힌다면, 되돌아가서 다시 해를 찾아가는 기법이다.
  • 해를 찾아가는 도중, 지금의 경로가 해가 될 것 같지 않다면 그 경로를 더이상 가지 않고 되돌아간다. 이것을 가지치기(Pruning) 이라고 한다.
  • 즉, 백트래킹은 모든 가능한 경우의 수 중에서 특정 조건을 만족하는 경우만 살펴보는 것이다. 
  • 해가 될 가능성이 있다면 유망하다(promising)고 한다.

 

N-Queen 문제

N-Queen 문제는 N X N 크기의 체스판에 N개의 퀸(Queen)을 서로 공격할 수 없도록 배치하는 문제이다. 이 때 퀸은 상하좌우, 대각선 방향으로 체스판의 끝까지 공격을 할 수 있다. 따라서 아래 그림처럼 직선, 대각선 상에는 퀸을 배치 할 수 없다.

N-Queen 문제는 N의 크기가 작다면 DFS 방식으로 모든 경우의 수를 탐색하면 답을 찾을 수 있다. 하지만 N의 크기가 커질 수록 탐색해야할 경우의 수가 기하급수적으로 증가한다. 아래 그림을 살펴보자.

상태 공간 트리

위 그림은 4 x 4 퀸 문제에서의 상태 공간 트리(State Space Tree)를 나타낸다. 여기서 상태 공간 트리란 모든 가능한 후보 해결법을 트리로 나타낸 것을 의미한다. 각 노드는 (행의 위치, 열의 위치)를 나타낸다. 예를 들어 (2,3) 은 2행 3열을 의미한다. 위와 같이 모든 경우의 수, 즉 퀸을 모든 위치에 배치해보는 방법은 O(N^N)이 걸린다. 매우 비효율적이다.

 

 

하지만 백트래킹을 이용하여 가지치기를 한다면 어떨까?

제일 먼저 (1,1) 에 퀸을 배치하고, 2행에서 퀸을 어디에 배치할 지 결정한다고 하자. 이 때 (2,1)은 직선상이고 (2,2)는 대각선상이라서 유망하지 않으므로(Not Promising) 해당 노드로 경로를 탐색하지 않는다(Pruning). 이후 (2,3)을 선택한다고 하자.

 

위 그림을 보면 3행에는 아무데도 퀸을 배치할 수 없는 것을 알 수 있다. 따라서 이 경로를 더 이상 탐색하지 않고 다시 2행으로 돌아가 유망한(promising) 노드인 (2,4)를 선택한다. 

 

위 그림을 보면 (3,1) (3,3) (3,4) 에는 퀸을 배치 할 수 없는 것을 알 수 있다. 따라서 이 경로를 더 이상 탐색하지 않고 유망한 노드인 (3,2)를 선택한다.

 

위 그림을 보면 4행에 어디에도 퀸을 배치 할 수 없는 것을 알 수 있다. 이 때 (1,1) 을 선택했을 경우 어떤 경우에도 퀸을 제대로 배치 할 수 없는 것을 확인했으므로 1행으로 돌아가 (1,2)를 선택한 후 다시 탐색을 진행하면 된다.

 

위와 같이 계속해서 유망한 노드 탐색, 가지치기를 진행하며 탐색하면 아래와 같은 결과를 얻을 수 있다.

이렇게 백트래킹을 이용하여 N-Queen 문제를 해결할 경우, 단순 DFS 방식보다 훨씬 효율적으로 답을 찾아 낼 수 있다.

 

 

C언어 구현

C언어로 N-Queen 문제를 구현해보자. 먼저 n*n 크기의 체스판에 어디에 퀸을 둘 지 저장할 배열이 필요하다. 이 때 많이들 2차원 배열을 생각하지만 그럴 필요없이 1차원 배열이면 충분하다. 그 이유가 무엇일까? N-Queen 문제는 무조건 한 행에  하나의 퀸을 배치하게 된다. 따라서 예를 들어 4x4 체스판에서 다음과 같이 퀸의 위치를 나타낼 수 있다.

col[1] = 2 : 1행 2열에 퀸이 있다.
col[2] = 4 : 2행 4열에 퀸이 있다.
col[3] = 1 : 3행 1열에 퀸이 있다.
col[4] = 3 : 4행 3열에 퀸이 있다.

 

 

다음으로 상태공간트리를 어떻게 구현하면 좋을까? 트리를 구현하는 방식도 있겠지만 굳이 그렇게 할 필요 없이 DFS처럼 재귀 방식을 사용하면 된다.

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
void printQueens()
{
    int i;
    printf(" ");
    for (i = 1; i <= n; i++)
        printf("(%d, %d)\n ", i, col[i]);
    exit(1);
}
 
void queens(int i)
{
    int j;
 
    if (promising(i))    // 유망하다면  
    {
        if (i == n)    // 마지막 행까지 선택이 끝났다면 
        {
            printQueens();    // 퀸의 위치 출력 
            return;
        }
        else    // 아직 끝나지 않았다면 
        {
            for (j = 1; j <= n; j++)    // 모든 열 탐색 
            {
                col[i + 1= j;
                queens(i + 1);
            }
        }
    } 
 
}
cs

 

가장 중요한 것은 해당 노드가 유망한지 체크하는 promising() 함수이다. 이 함수는 해당 위치에 퀸을 두면 상하좌우, 대각선 상에 겹치는지 체크하여 값을 반환하는 함수이다. 

1
2
3
4
5
6
7
8
9
10
11
12
int promising(int i)    // 유망한지 체크 
{
    int k = 1;
    int flag = 1;
    while (k < i)
    {
        if (col[i] == col[k] || abs(col[i] - col[k]) == abs(i - k))    // 상하좌우, 대각선 상에 있는 지 
            flag = 0;
        k++;
    }
    return flag;
}
cs

 

전체 소스코드

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
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
int n;    // n x n 보드판의 크기 n 
int *col;    // 각 행별로 퀸의 열(column) 위치 
 
void printQueens()
{
    int i;
    printf(" ");
    for (i = 1; i <= n; i++)
        printf("(%d, %d)\n ", i, col[i]);
    exit(1);
}

int promising(int i)    // 유망한지 체크 
{
    int k = 1;
    while (k < i)
    {
        if (col[i] == col[k] || abs(col[i] - col[k]) == abs(i - k))    // 상하좌우, 대각선 상에 있는 지 
          return 0;
        k++;
    }
    return 1;
}


void queens(int i)
{
    int j;
 
    if (promising(i))    // 유망하다면  
    {
        if (i == n)    // 마지막 행까지 선택이 끝났다면 
        {
            printQueens();    // 퀸의 위치 출력 
            return;
        }
        else    // 아직 끝나지 않았다면 
        {
            for (j = 1; j <= n; j++)    // 모든 열 탐색 
            {
                col[i + 1= j;
                queens(i + 1);
            }
        }
    } 
 
}
 
 
int main()
{
    printf("Input N: ");
    scanf("%d"&n);
    col = (int*)malloc(sizeof(int)*(n+1));
    queens(0);
    printf("No solution");    // 해결법이 없을 경우 
    
    return 0;
}
cs

 

 

실행결과

 

 


 

반응형

댓글

Designed by JB FACTORY