본문으로 건너뛰기
김신건의 로그

Brute Force: 완전 탐색

· 수정 · 📖 약 3분 · 972자/단어 #algorithm #foundation #search #backtracking
Brute Force, 완전 탐색, brute force, 완전탐색, exhaustive search

정의

Brute Force (완전 탐색) 는 문제의 모든 후보 해를 하나씩 검증하는 접근. 최적은 아니지만 정확하고 명확하며, 큰 문제에도 부분 검증 도구로 사용.

핵심 원칙: 모든 가능성을 빠짐없이 탐색하되, 불필요한 탐색은 가지치기로 제거.

문제 상황

완전 탐색이 유효한 상황:

  • N ≤ 20: 부분집합 열거 (2^N ≤ 10^6)
  • N ≤ 10: 순열 열거 (10! ≈ 3.6×10^6)
  • N ≤ 8: 순열 열거 (8! = 40,320, 매우 빠름)
  • N ≤ 1000: 이중 루프 (10^6)
  • N ≤ 500: 삼중 루프 (1.25×10^8, 타이트)

완전 탐색으로 정답을 구한 뒤, 최적화 알고리즘의 정답 검증 도구로도 활용.

시각화

완전 탐색에서 최적화로의 발전 경로:

flowchart TD
    BF["완전 탐색 (Brute Force)"]
    P["가지치기 추가 (Pruning)"]
    BB["Branch and Bound"]
    Memo["메모이제이션 추가"]
    DP["동적 프로그래밍 (DP)"]
    Bit["비트마스크 인코딩"]
    BitmaskDP["Bitmask DP"]

    BF -->|"반복 계산 제거"| Memo
    BF -->|"탐색 공간 제한"| P
    P --> BB
    Memo --> DP
    BF -->|"집합 상태 인코딩"| Bit
    Bit --> BitmaskDP

핵심 아이디어

패턴 1: 부분집합 열거 (2^N)

for mask = 0 to (1<<N)-1:
    subset = {i : mask & (1<<i) != 0}
    check(subset)

패턴 2: 순열 열거 (N!)

arr = [0, 1, ..., N-1]
do:
    check(arr)
while next_permutation(arr)

패턴 3: 재귀 백트래킹

function backtrack(state, depth):
    if depth == N:
        check(state)
        return
    for choice in choices:
        if is_valid(state, choice):
            state.add(choice)
            backtrack(state, depth + 1)
            state.remove(choice)

패턴 4: 중첩 루프

for i = 0 to N-1:
    for j = i+1 to N-1:
        check(i, j)

알고리즘

가지치기 (Pruning)

현재 부분 해가 최적 해보다 나쁠 수 없을 때 탐색 중단:

function backtrack(state, depth, current_cost):
    if current_cost >= best_cost: return  # 가지치기
    if depth == N:
        best_cost = min(best_cost, current_cost)
        return
    for choice in choices:
        backtrack(state + choice, depth + 1, current_cost + cost(choice))

Meet in the Middle

N이 40 정도일 때: 절반씩 나눠 2^20 × 2 = 2×10^6 으로 해결.

left_results = enumerate_all(first_half)
right_results = enumerate_all(second_half)
sort(right_results)
for each l in left_results:
    binary_search(right_results, target - l)

구현

#include <bits/stdc++.h>
using namespace std;

int n;
vector<int> arr;

// 패턴 1: 부분집합 열거
void subset_enumeration() {
  for (int mask = 0; mask < (1 << n); mask++) {
      cout << "{ ";
      for (int i = 0; i < n; i++)
          if (mask & (1 << i))
              cout << arr[i] << " ";
      cout << "}\n";
  }
}

// 패턴 2: 순열 열거
void permutation_enumeration() {
  sort(arr.begin(), arr.end());
  do {
      for (int x : arr) cout << x << " ";
      cout << "\n";
  } while (next_permutation(arr.begin(), arr.end()));
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  cin >> n;
  arr.resize(n);
  for (int i = 0; i < n; i++) cin >> arr[i];

  int mode;
  cin >> mode;

  if (mode == 1) subset_enumeration();
  else permutation_enumeration();

  return 0;
}
stdin
3
1 2 3
1
결과
{ }
{ 1 }
{ 2 }
{ 1 2 }
{ 3 }
{ 1 3 }
{ 2 3 }
{ 1 2 3 }

복잡도

탐색 유형복잡도N 한계 (10^8 기준)
부분집합O(2^N)N ≤ 26
순열O(N!)N ≤ 12
조합 C(N,K)O(C(N,K))상황별
이중 루프O(N²)N ≤ 10^4
삼중 루프O(N³)N ≤ 500
Meet in MiddleO(2^(N/2))N ≤ 40

최적화로 발전

1. 가지치기 → Branch and Bound

현재 부분 해가 최적 해보다 나쁠 수 없을 때 탐색 중단. 최악 복잡도는 동일하지만 실제로 훨씬 빠름.

2. 메모이제이션 → DP

같은 부분 문제를 반복 계산하면 메모이제이션으로 DP 변환. 지수 → 다항식 복잡도.

3. 비트마스크 → Bitmask DP

집합 상태를 비트마스크로 인코딩하면 O(N!) → O(2^N · N²) (TSP 예시).

4. 너비 우선 탐색 결합

상태 공간 탐색에서 BFS로 최단 경로 탐색.

함정

1. 시간 초과 (TLE)

N=20 순열 (20! ≈ 2.4×10^18)은 절대 불가. N 제약 확인 필수.

WARNING

완전 탐색 전 반드시 복잡도 계산. N=15 순열 (15! ≈ 1.3×10^12)도 TLE.

2. 중복 탐색

순열에서 같은 원소가 있으면 중복 제거 필요. next_permutation은 정렬된 배열에서 시작해야 모든 순열을 정확히 한 번씩 생성.

3. 가지치기 조건 오류

가지치기 조건이 너무 강하면 정답을 놓침. 너무 약하면 효과 없음.

4. 재귀 깊이 초과

Python에서 재귀 깊이 기본 1000. sys.setrecursionlimit(10**6) 설정 필요.

5. 인덱스 범위 오류

부분집합 열거에서 1 << N이 int 범위를 초과할 수 있음. N=31 이상이면 1LL << N 사용.

BOJ 연습 문제

번호제목유형
BOJ 1182부분수열의 합부분집합 열거
BOJ 9663N-Queen백트래킹
BOJ 15649N과 M (1)순열
BOJ 2309일곱 난쟁이부분집합
BOJ 1759암호 만들기조합
BOJ 1208부분수열의 합 2Meet in Middle

관련 위키

이 글의 용어 (3개)
너비 우선 탐색 (BFS)algorithm
정의 너비 우선 탐색 (Breadth-First Search, BFS) 는 그래프 G=(V, E) 에서 시작 정점 s 로부터 가까운 정점부터 순서대로 방문하는 알고리즘. 큐 (F…
비트마스크 DP (Bitmask DP)algorithm
정의 비트마스크 DP (Bitmask DP) 는 상태 공간이 부분집합 으로 표현될 때, 각 부분집합을 정수의 비트로 인코딩해 DP 상태로 삼는 기법. N ≤ 20 범위에서 O(2…
Branch and Bound: 분기 한정algorithm
정의 Branch and Bound (B&B) 는 완전 탐색을 하되, 상한/하한을 계산해 유망하지 않은 서브트리를 가지치기 하는 기법. 최적화 문제에 적용. 백트래킹이 "실행 불…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기