읽기 전
- 해당 게시물은 작성자가 학습한 내용을 정리 및 요약 목적으로 작성되었으므로 오역 및 오개념이 있을 수 있습니다.
- 바로잡기 위한 의견을 제시해주시면 매우 감사하겠습니다.
Backtracking(퇴각 검색)
Backtracking(퇴각 검색)은 정답을 찾을 때까지 다른 해답을 시도해보는 알고리즘이다. 보통 다른 알고리즘이 아닌 backtracking이 해법이 ㄴ경우 대부분 모든 경우의 수를 시도해야 해결되는 문제가 많다. Naive한 방법은 모든 경우의 수를 대입하겠지만 시간 복잡도의 문제로 인해 제시된 문제에서 파악할 수 있는 제약 조건이 있는 경우 모두 적용해서 최적화를 시도해야 한다.
나이트가 주어진 셀을 모두 거칠 수 있는 경로

그림과 같이 8*8 맵이 주어졌을 때, 셀에 적인 번호는 Knight 말이 몇 번째 움직임에 해당 셀을 지나갔음을 의미한다. 우리는 빈 맵이 주어지고 시작점이 (0,0)인 상황에서 Knight가 어떻게 움직여야 하는지 제시해야 한다.
Knight's tour에 대한 Naive Algorithm
- 만약 시도하지 않은 경로가 있다면 경로를 생성한다
- 해당 경로가 모든 셀을 지나가면 출력한다.
Knight's tour에 대한 backtracking Algorithm
- 만약 모든 셀을 지나가는 경로를 발견했다면 경로를 출력한다.
- 움직임 1회를 더할 때 해당 지점이 제한 요건을 위배하는지 검사한 뒤 위배된다면 경우의 수를 지우고 다음 대안으로 진행한다. 위배되지 않는다면 말을 움직인 뒤 재탐색한다.
- 계속 반복하여 진행하였으나 모든 대안이 실패한 경우 이전 단계로 돌아가 움직였던 위치를 초기화하여 다음 대안을 시도한다.
- 이런 식으로 2번의 넓이, 3번의 깊이 탐색을 모두 진행하였음에도 처음으로 돌아온 경우 ''해결법이 없다'라고 말할 수 있다.
C코드
| #include <stdio.h> |
| #define N 8 |
| |
| int solveKTUtil(int x, int y, int movei, int sol[N][N], |
| int xMove[], int yMove[]); |
| |
| int isSafe(int x, int y, int sol[N][N]) |
| { |
| return (x >= 0 && x < N && y >= 0 && y < N && sol[x][y] == -1); |
| } |
| |
| void printSolution(int sol[N][N]) |
| { |
| for (int x = 0; x < N; x++) |
| { |
| for (int y = 0; y < N; y++) |
| { |
| printf(" %2d", sol[x][y]); |
| } |
| printf("\n"); |
| } |
| } |
| |
| int solveKT() |
| { |
| int sol[N][N]; |
| |
| for (int x = 0; x < N; x++) |
| { |
| for (int y = 0; y < N; y++) |
| { |
| sol[x][y] = -1; |
| } |
| } |
| |
| |
| int xMove[8] = { 2, 1, -1, -2, -2, -1, 1, 2 }; |
| int yMove[8] = { 1, 2, 2, 1, -1, -2, -2, -1 }; |
| |
| sol[0][0] = 0; |
| |
| |
| if (solveKTUtil(0, 0, 1, sol, xMove, yMove) == 0) |
| { |
| printf("There's no Solution"); |
| return 0; |
| } |
| else |
| { |
| printSolution(sol); |
| } |
| return 1; |
| } |
| |
| |
| |
| int solveKTUtil(int x, int y, int movei, int sol[N][N], |
| int xMove[8], int yMove[8]) |
| { |
| int k, next_x, next_y; |
| if (movei == N*N) |
| { |
| return 1; |
| } |
| |
| for (k = 0; k < 8; k++) |
| { |
| next_x = x + xMove[k]; |
| next_y = y + yMove[k]; |
| |
| if (isSafe(next_x, next_y, sol)) |
| { |
| sol[next_x][next_y] = movei; |
| if (solveKTUtil(next_x, next_y, movei+1, sol, |
| xMove, yMove) == 1) |
| { |
| return 1; |
| } |
| else |
| { |
| sol[next_x][next_y] = -1; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| int main() |
| { |
| solveKT(); |
| return 0; |
| } |
Python 코드
| def isSafe(x,y,board): |
| if(x >= 0 and y >= 0 and x < n and y < n and board[x][y] == -1): |
| return True |
| return False |
| |
| def printSolution(n, board): |
| for i in range(n): |
| for j in range(n): |
| print(board[i][j],end =' ') |
| print() |
| |
| |
| def solveKT(n): |
| board = [[-1 for i in range(n)]for i in range(n)] |
| |
| move_x = [2, 1, -1, -2, -2, -1, 1, 2] |
| move_y = [1, 2, 2, 1, -1, -2, -2, -1] |
| |
| board[0][0] = 0 |
| pos = 1 |
| |
| if(not solveKTUtil(n, board, 0, 0, move_x, move_y, pos)): |
| print("Solution does not exist") |
| else: |
| printSolution(n, board) |
| |
| def solveKTUtil(n,board,curr_x,curr_y,move_x,move_y,pos): |
| if(pos == n**2): |
| return True |
| |
| for i in range(8): |
| new_x = curr_x + move_x[i] |
| new_y = curr_y + move_y[i] |
| if(isSafe(new_x,new_y,board)): |
| board[new_x][new_y] = pos |
| if(solveKTUtil(n, board, new_x, new_y, |
| move_x, move_y, pos+1)): |
| return True |
| board[new_x][new_y] = -1 |
| return False |
| |
| n = 8 |
| solveKT(n) |
C++ 코드
| #include <bits/stdc++.h> |
| using namespace std; |
| #define N 8 |
| |
| int solveKTUtil(int x, int y, int movei, int sol[N][N], |
| int xMove[], int yMove[]); |
| |
| int isSafe(int x, int y, int sol[N][N]) |
| { |
| return (x >= 0 && x < N && y >= 0 && y < N && sol[x][y] == -1); |
| } |
| |
| void printSolution(int sol[N][N]) |
| { |
| for (int x = 0; x < N; x++) |
| { |
| for (int y = 0; y < N; y++) |
| { |
| cout << setw(2) << sol[x][y] << " "; |
| } |
| cout << endl; |
| } |
| } |
| |
| int solveKT() |
| { |
| int sol[N][N]; |
| |
| for (int x = 0; x < N; x++) |
| { |
| for (int y = 0; y < N; y++) |
| { |
| sol[x][y] = -1; |
| } |
| } |
| |
| int xMove[8] = { 2, 1, -1, -2, -2, -1, 1, 2 }; |
| int yMove[8] = { 1, 2, 2, 1, -1, -2, -2, -1 }; |
| |
| sol[0][0] = 0; |
| |
| if (solveKTUtil(0, 0, 1, sol, xMove, yMove) == 0) |
| { |
| cout << "There's no Solution"; |
| return 0; |
| } |
| else |
| { |
| printSolution(sol); |
| } |
| return 1; |
| } |
| |
| int solveKTUtil(int x, int y, int movei, int sol[N][N], |
| int xMove[], int yMove[]) |
| { |
| int k, next_x, next_y; |
| if (movei == N*N) |
| { |
| return 1; |
| } |
| |
| for (k = 0; k < 8; k++) |
| { |
| next_x = x + xMove[k]; |
| next_y = y + yMove[k]; |
| if (isSafe(next_x, next_y, sol)) |
| { |
| sol[next_x][next_y] = movei; |
| if (solveKTUtil(next_x, next_y, movei + 1, sol, |
| xMove, yMove) == 1) |
| { |
| return 1; |
| } |
| else |
| { |
| sol[next_x][next_y] = -1; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| int main() |
| { |
| solveKT(); |
| return 0; |
| } |
결국 backtracking 알고리즘을 구현하는 코드는 다음 순서로 진행된다.
- 그리고 조건을 검사하는 함수와 출력 함수를 정의한다.
- 메인 함수를 선언한 뒤 모든 조건들을 정의한다. 그리고 초기 조건을 설정하여 재귀 탐색을 시작한다.
- 마지막으로 재귀함수를 선언하며 재귀 함수의 목적은 이전 단계의 탐색을 성공하여 현재 단계의 경우의 수를 시도하는 함수이므로 현재 단계가 시도할 수 있는 가지 수를 모두 적용한 뒤 제한 요건에 위배되지 않는 경우 다음 단계를 탐색하도록 재귀함수를 구성한다. 그리고 이후 단계에서 결과가 나오지 않아 회귀된 경우 backtracking을 하여 다음 경우의 수를 대입한다. 그리고 마지막으로 False를 반환하여 모든 경우의 수를 대입했음에도 답이 없었음을 반환한다.
참고자료
- The Knight’s tour problem | Backtracking-1