백트래킹(Backtracking)은 재귀(Recursion)를 이용하여 모든 가능한 경우의 수를 탐색하는 알고리즘 중 하나입니다. 각 단계에서 가능한 모든 선택지를 시도해보고, 만약 잘못된 선택을 했다면 그 선택 이전 상태로 돌아가서 다른 선택지를 시도합니다. 이를 "되돌아가기" 또는 "백트래킹"이라고 부릅니다.
백트래킹은 문제 해결 과정에서 조건을 만족하지 않는 경로는 더 이상 탐색하지 않고 가지치기(pruning) 하는 것이 핵심입니다. 이를 통해 불필요한 탐색을 줄여서 효율적으로 해결할 수 있습니다.
백트래킹을 단계별로 나누어 설명하면 이해하기가 쉽습니다.
백트래킹 알고리즘은 크게 세 단계로 나눌 수 있습니다.
백트래킹 알고리즘에서 1번은 재귀 호출을 통해 구현되며, 2번과 3번은 재귀 호출 안에서 처리됩니다.
이 부분은 백트래킹의 핵심입니다. 문제의 각 단계에서 여러 가지 선택을 할 수 있을 때, 그 선택들을 하나씩 시도하는 과정입니다. 이때 선택할 수 있는 모든 경우를 재귀적으로 탐색합니다.
dfs(depth)
함수는 현재 깊이에서 가능한 숫자를 선택한 후, 그 숫자에 대해 다시 다음 깊이로 이동하는 방식으로 동작합니다.이 부분은 각 선택이 유효한지 판단하는 역할을 합니다. 유효하지 않으면 더 이상 진행하지 않고 선택을 취소하고, 유효하면 계속 진행합니다.
visit[]
배열을 사용하여 방문 여부를 확인하는 것이 이 과정입니다.이 부분은 재귀 호출이 더 이상 진행되지 않고 종료되도록 하는 조건입니다. 주로 재귀 호출의 깊이가 일정 값에 도달했을 때, 즉 문제에서 요구하는 해의 조건을 만족했을 때 종료됩니다. 문제를 완전히 해결했으면 결과를 출력하거나 저장합니다.
depth == m
이 될 때 모든 숫자를 선택한 것이므로 출력하는 부분이 여기 해당합니다.public class Main {
static int[] arr; // 선택된 숫자들을 저장할 배열
static boolean[] visit; // 숫자의 방문 여부를 저장할 배열
static int n, m;
// 1. 백트래킹 함수 (문제 해결을 위한 선택지 제시)
public static void dfs(int depth) {
// 3. 종료 조건: M개의 숫자를 모두 선택했을 때 결과를 출력하고 종료
if (depth == m) {
for (int i = 0; i < m; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
return;
}
// 1. 가능한 선택지(1부터 N까지의 숫자)를 시도
for (int i = 1; i <= n; i++) {
// 2. 해가 유효한지 확인 (이미 선택된 숫자는 다시 선택하지 않음)
if (!visit[i]) {
visit[i] = true; // i를 선택
arr[depth] = i; // 현재 깊이에 선택한 숫자를 저장
dfs(depth + 1); // 재귀 호출로 다음 깊이로 이동 (선택지 제시)
visit[i] = false; // 백트래킹: 선택을 취소하고 다시 다른 선택 시도
}
}
}
public static void main(String[] args) {
// 입력 받기 및 초기화
n = 3; // N 값
m = 2; // M 값
arr = new int[m];
visit = new boolean[n + 1]; // 1부터 n까지 사용하므로 크기 n+1
dfs(0); // 깊이 0부터 시작 (최초 재귀 호출)
}
}
dfs(depth + 1)
로 다음 깊이로 넘어가는 것이 재귀 호출입니다. 모든 가능한 선택지를 탐색합니다.if (!visit[i])
조건으로 선택한 숫자가 유효한지(이미 방문한 숫자인지) 확인하고, 유효하지 않으면 가지치기를 합니다.if (depth == m)
에서, 해가 완성되었을 때 결과를 출력하고 종료합니다.이처럼 1번이 재귀 호출을 통한 선택의 확장이고, 2번과 3번이 재귀 호출 안에서 각각 유효성 검사와 종료 조건으로 구현됩니다.
백트래킹 알고리즘을 이해하는 가장 좋은 방법은 실제 문제에 적용해 보는 것입니다. 다음은 백트래킹을 적용할 수 있는 대표적인 문제들입니다.
1부터 N까지의 자연수 중에서 중복 없이 M개의 수를 고르는 모든 경우를 출력하는 문제입니다.
입력: N = 3, M = 2
출력:
1 2
1 3
2 1
2 3
3 1
3 2
public class Main {
static int[] arr; // 선택된 숫자들을 저장할 배열
static boolean[] visit; // 숫자의 방문 여부를 저장할 배열
static int n, m;
// 백트래킹 함수
public static void dfs(int depth) {
if (depth == m) { // M개의 숫자를 모두 선택했을 때 출력
for (int i = 0; i < m; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
return;
}
for (int i = 1; i <= n; i++) {
if (!visit[i]) { // 선택되지 않은 숫자를 찾음
visit[i] = true; // 방문 처리
arr[depth] = i; // 선택된 숫자를 배열에 저장
dfs(depth + 1); // 재귀 호출을 통해 다음 숫자를 선택
visit[i] = false; // 백트래킹: 방문 처리 해제
}
}
}
public static void main(String[] args) {
// N과 M을 입력받고 초기화
n = 3;
m = 2;
arr = new int[m];
visit = new boolean[n + 1];
dfs(0); // 깊이 0부터 백트래킹 시작
}
}
백트래킹 알고리즘은 다음과 같은 문제들에서 자주 사용됩니다.
백트래킹은 여러 선택지가 주어진 문제에서 조건을 만족하는 해를 찾기 위한 강력한 알고리즘입니다. 모든 경우를 탐색할 때 적합하며, 가지치기를 통해 불필요한 경로를 탐색하지 않아 효율적으로 문제를 해결할 수 있습니다.