백트래킹

정답 후보를 만들어가다가 조건을 위반하면 되돌아가는 알고리즘 기법

용어

  • 유망(promising) : 현재 상태(노드, 경로)가 문제의 해답으로 발전한 가능성이 높은지를 판단하는 기준
  • 가지치기(pruning) : 탐색 중 불필요한 경로를 제거하여 탐색의 효율성을 높이는 방법

백트래킹 구성요소

  • 선택(Decision)
    • 현재까지 만든 ‘부분 해’에 하나를 더 붙여서 다음 단계로 이동하는 행위
  • 조건(Constraint)
    • 선택한 결과가 규칙을 위반하거나 목표 달성이 불가능해 보이면, 더 이상 깊게 진행하지 않고 되돌아가는 판단 즉시 위반 조건 (해당 선택이 규칙, 조건을 위반함)
    • 불가능한 조건 (지금 위반은 아니지만 앞으로의 과정에서 해답을 만들 수 없음)
  • 종료(Goal)
    • 정답 조건을 만족하는 완성 상태에 도달했을 때 처리하는 부분
    • 정답을 찾으면 ‘카운트/저장’하고 돌아감
    • 정답을 찾으면 ‘즉시 종료’할 수도 있음
    • 정답을 카운팅 하는지, 저장하는지, 바로 종료하는지를 구분하여 처리

백트래킹 의사코드 예시

function solve(현재상태):
  [종료] 정답이 완성되었나?
    정답 처리(카운트/저장/출력/return)

  for 각 선택지 in 가능한 선택들:
    [조건]이 선택 가능한가?
      가능하면:
        선택 적용(체크/배열 갱신)
        solve(다음상태)
        선택 복구(원상복구)

백트래킹이 필요한 문제 특징

이 조건 만족 시 백트래킹을 고려해볼 필요가 있다.

  • 경우의 수가 많음
  • 하나씩 선택하면서 정답을 만들어간다
  • 중간에 조건 위반을 판단할 수 있다
  • 조건 위반이면 더 진행할 필요가 없다

백트래킹 문제 유형

순열 : N개의 숫자를 순서 있게 나열(중복 없음) -> 이미 사용한 숫자를 다시 사용하지 않음 조합 : N개 중 R개를 선택하고, 순서 상관 없음 -> 뽑고 남은 개수가 R개를 채울 수 없으면 중단 부분집합 : 각 원소를 선택/비 선택하는 모든 경우의 생성 -> 부분 집합의 합, 조건을 만족하는 집합이 없다면 중단

조건 만족 배치 문제

  • 공간(배열, 격자 등)에 배치
  • 서로 충돌하면 안됨
  • N-Queen
  • 스도쿠
  • 그래프 색칠 (인접 노드 같은 색 x) 문제

목표값 만들기 (합/식/카운트)

  • 숫자들을 이용한 형태
  • 합이 K개 되는
  • 목표값을 만들 수 있는지
  • 경우의 수 카운팅

문자열/숫자 생성 문제

  • 특정 조건을 만족하는 문자열 생성
  • 숫자 자릿수 구성
  • 비밀번호 생성 문제
  • 숫자 이어 붙이기 문제
  • 중복 없는 문자열 만들기 문제

경로/이동 문제

  • 시작 지점에서 도착까지 경로 찾기
  • 탈출할 수 있는지 확인하는 문제

N-Queen 문제

N x N 크기의 체스판 위에 N개의 Queen들을 서로 위협하지 않게 배치하는 문제

문제 링크 : N-Queen

  • 8x8 크기의 체스 판 위에 8개의 Queen들을 서로 위협하지 않게 배치하는 모든 경우의 수를 구하는 문제
  • 모든 경우의 수는
  • 실제 정답의 수는 92개
  • 모든 경우의 수 속에서 92개를 최대한 효율적으로 찾아내는 것이 관건
import java.util.*;
 
public class Main{
	static int answer = 0;
	static int N;
	static int[] visited;
	static int[][] arr;
 
  // 백트래킹
	static void BT(int depth) {
		if (depth==N) {
			answer++;
			return;
		}
		for (int i=0; i<N;i++) {
			if (search(depth,i)) {
				visited[depth] = i;
				BT(depth+1);
				visited[depth] = -1;
			}
		}
	}
 
  // 겹치는지 확인 (겹치면 false를 반환)
	static boolean search(int i, int j) {
		for (int y=0; y<i; y++) {
			if (visited[y]==j) return false;
 
			if (Math.abs(i-y) == Math.abs(j - visited[y])) return false;
		}
		return true;
	}
	
	public static void main(String[] args) {
    // 할당
		Scanner sc = new Scanner(System.in);
		N = sc.nextInt();
		visited = new int[N];
		Arrays.fill(visited, -1);
    // 백트래킹
		BT(0);
		System.out.println(answer);
	}
}