다음과 같은 그래프가 있다.
여기서 1부터 시작해서 숫자 순서대로 얼마나 멀리 갈 수 있는지 찾고싶다. 즉 1->2->3->4->5->... 이런식의 루트를 찾으려 한다.
우선 생각해볼 수 있는 방법은, 그냥 모든 루트에 대해 DFS 등을 사용해 전부 탐색해 보는 것이다. 이 방식이 이전에 작성한 Brute Force 방식이다. 그냥 컴퓨터의 빠른 연산력에 기대어 모든 경우를 확인하는 것이다.
위는 모든 경우를 살펴보는 DFS 과정의 순서를 표시한 것이다. 모든 경우를 확인해보니 결국 1->2->3->4를 찾긴했다.
그런데 우린 직관적으로, 중간과정에서 이미 순서가 안맞다면 해당 루트로는 아예 안가봐도 될 것임을 알 수 있다. 그럼 다음 이미지를 보자.
위와 같이 답이 될 가능성이 없는 경로라면 더이상 살펴볼 이유 자체가 없다. 위 이미지를 순서대로 말로 설명하면 다음과 같다.
순서1 : 진행해보니 '3'이 있다.
순서2 : 1->3은 그 뒤에 무엇이 있던 순서대로 진행이 불가하므로 되돌아간다. (Back)
순서3 : 다른 길로 다시 진행한다. (Tracking) 1->2 이므로 만족하는 경우이다. 계속 진행해본다.
순서4 : 1->2->4가 되었다.
순서5 : 찾는 경우가 아니므로 다시 되돌아간다. (Back)
순서6 : 다시 이전 지점에서 다른 경로로 진행한다. (Tracking) 1->2->3 이므로 찾는 경우이다. 계속 가본다.
순서7 : 1->2->3->4를 찾았다. 더이상 되돌아가서 진행할 곳 없으므로 종료한다.
이처럼 답이 될 가능성이 없는 경로인 경우, 다시 이전 단계로 되돌아가서(Back) 다른 경로를 따라가는 것(Tracking)이 바로 백트레킹(Back Tracking)이다. 위 예시에서는 횟수차이가 얼마 안났지만, 예를들어 노드가 1억개 있는 그래프라면 얼마나 차이가 날지 상상할 수 있을 것이다.
사실 Brute Force로 모든 경우를 보는 경우를 생각해보고, 거기서 쳐낼 수 있는 가능성이 있다면 우린 직관적으로 그렇게 하는게 더 좋다는걸 알고있다. 그래서 굳이 나눠서 생각해서 이건 브루트 포스고, 저건 백트래킹이야! 이럴 필요는 없다고 본다. 그냥 모든 경우를 살펴보는 로직을 생각해보고, 중간에 쳐낼 수 있는 경우가 있다면 쳐내자! 최악의 경우엔 어차피 모든 경우를 보게 되겠지만, 일반적으로는 더 효율성 좋은 코드가 된다.
백트레킹은 다양한 방식으로 사용할 수 있다. 한 가지 예시를 더 보자.
시작지점에서 최대 4번까지 이동하면서 당근을 수집하려고 한다. (아무튼 당근임. 그림 못그림.)
처음에 1번루트로 가보니 3개를 발견했다. 그럼 2루트로 진행해서 A지점까지 왔을 때, 더이상 해당 경로를 살펴볼 필요가 있을까? A 지점에 왔을 때 이미 2칸을 왔고, 나머지 2칸에 모두 당근이 있다 해도 최대 2개다. 그러므로 더이상 진행할 필요가 없으니 되돌아와서 루트3으로 간다. 마찬가지로 B까지 왔을 때 더이상 살펴보지 않아도 된다.
다음은 백트래킹으로 유명한 N-Queen이라는 문제다. N이라는 숫자에 대해 N x N 크기의 체스판에 N개의 Queen을 서로 공격할 수 없도록 놓는 문제이다.
위는 N = 4 일 때 놓을 수 있는 경우 중 하나이다. 이 경우 모든 경우를 본다면, 각 칸마다 퀸을 놓거나, 안놓거나 둘 중 하나 이므로 O(2^(4x4))가 된다. 겨우 4x4 짜리 체스판인데도 모든 경우는 65,536가지나 된다. 하지만 백트레킹으로 생각해본다면 다음과 같이 연산을 줄일 경우를 생각해볼 수 있다.
1. N x N 크기에 N개를 놓아야하는데, 퀸은 상하좌우로 전체를 공격할 수 있으므로 일단 동일 row, 동일 column에는 퀸을 놓을 수 없다! 즉 각 row와 각 column에는 무조건 하나씩의 퀸만 존재한다.
-> '2'까지 하지 않고, 이것만 해도 연산이 엄청나게 줄어든다.
2. 대각선으로도 놓을 수 없으므로 대각선에 대한 체크도 해볼 수 있다. 배열 형태로 체스판을 만들고 어떠한 위치에 놓았다면 실제로 배열에 체크해서 확인해도 되고, 그냥 계산식을 짜서 확인해도 된다. (1번 퀸과 2번 퀸에 대해 두 퀸의 위치의 좌우 차이와 상하 차이의 절대값이 동일하다면 대각선이라고 볼 수 있다.)
한번 직접 풀어보고 싶다면 https://www.acmicpc.net/problem/9663 이걸 풀어보면 된다.
끝.
'CS > Algorithms' 카테고리의 다른 글
누적 합(prefix sum), 2차원 누적합(prefix sum of matrix) with java (10) | 2022.08.09 |
---|---|
분할 정복을 이용한 거듭제곱 최적화 (0) | 2022.04.05 |
에라토스테네스의 체 혹은 소수판정 시 제곱근 까지만 확인하면 되는 이유 (4) | 2022.02.12 |
BFS 알고리즘 (너비 우선 탐색) - 배열 BFS, 그래프 BFS (2022-08-27 업데이트) (10) | 2021.09.24 |
알고리즘 시간복잡도에 대해 (0) | 2021.09.22 |
댓글