개발이야기/파이썬

[Python - 알고리즘] 완전탐색

Study & Stack 2025. 5. 26. 18:35
728x90

완전탐색 (Brute Force) 상세 정리 및 응용 문제


1. 완전탐색이란?

완전탐색은 가능한 모든 후보해(해답)를 전부 탐색하여 문제를 해결하는 방법입니다.
말 그대로 '무식하게' 모든 경우를 시도해보는 방식이라서 구현이 쉽고 직관적입니다.

  • 모든 가능한 경우의 수를 하나하나 확인하며 정답을 찾습니다.
  • 입력 크기가 작거나 경우의 수가 제한적인 문제에 적합합니다.
  • 시간복잡도가 매우 클 수 있으므로, 입력 크기에 따라 비효율적일 수 있습니다.

예를 들어, n개의 원소에서 모든 부분집합을 구하는 경우, 경우의 수는 2^n 으로 증가합니다.
따라서 n이 커지면 완전탐색만으로 문제를 해결하기 어려워 최적화 기법이 필요합니다.


2. 완전탐색 응용 문제 및 코드 (주석 포함)

1) N-Queen 문제

N×N 크기의 체스판에 N개의 퀸을 서로 공격하지 못하도록 놓는 모든 경우의 수를 찾는 문제입니다.
퀸은 가로, 세로, 대각선 방향으로 공격할 수 있기 때문에 서로 공격하는 위치에 놓이면 안 됩니다.

def solve_n_queens(n):
    board = [-1] * n  # 각 행에 놓인 퀸의 열 위치를 저장 (board[i] = j 는 i번째 행에 j열에 퀸이 있음)
    solutions = []  # 가능한 모든 해답을 저장할 리스트

    def is_valid(row, col):
        # 현재 row 행에 col 열에 퀸을 놓을 수 있는지 검사
        for i in range(row):
            # 같은 열에 퀸이 있으면 안됨
            if board[i] == col:
                return False
            # 대각선 공격 방지: 행 차이와 열 차이가 같으면 대각선에 있음
            if abs(board[i] - col) == row - i:
                return False
        return True

    def dfs(row):
        # 모든 행에 퀸을 놓았으면 하나의 해답 완성
        if row == n:
            solutions.append(board[:])  # 현재 퀸 위치를 복사해 저장
            return

        # 현재 행(row)에서 모든 열(col)에 퀸 놓기 시도
        for col in range(n):
            if is_valid(row, col):
                board[row] = col  # 퀸 놓기
                dfs(row + 1)      # 다음 행으로 이동하여 탐색
                # 백트래킹: 탐색 후 돌아와서 다음 열 시도

    dfs(0)  # 0행부터 시작
    return solutions

# 예시 출력 (4-Queen 문제)
print(solve_n_queens(4))

2) 부분 집합의 합 문제 (Subset Sum)

주어진 배열에서 일부 원소를 골라 합이 목표값(target)과 같은 부분집합이 있는지 확인하는 문제입니다.
완전탐색으로 각 원소를 포함하거나 포함하지 않는 경우를 모두 시도합니다.

def subset_sum(nums, target):
    def dfs(i, current_sum):
        # 목표 합에 도달하면 True 반환
        if current_sum == target:
            return True

        # 배열 끝에 도달했거나 현재 합이 초과하면 False 반환
        if i == len(nums) or current_sum > target:
            return False

        # 현재 원소를 포함하는 경우와 포함하지 않는 경우 모두 탐색
        return dfs(i + 1, current_sum + nums[i]) or dfs(i + 1, current_sum)

    return dfs(0, 0)

# 예시 출력
print(subset_sum([3, 34, 4, 12, 5, 2], 9))  # True (4 + 5 = 9)

3) 문자열의 모든 순열 구하기

주어진 문자열의 문자들을 재배열하여 만들 수 있는 모든 순열을 찾는 문제입니다.
완전탐색 + 백트래킹 기법을 이용해 중복 없이 모든 순열을 생성합니다.

def permute(s):
    results = []
    s = list(s)  # 문자열을 리스트로 변환 (문자 교환을 위해)

    def dfs(start):
        # 모든 문자를 고정하면 순열 완성
        if start == len(s) - 1:
            results.append(''.join(s))  # 리스트를 문자열로 변환하여 저장
            return

        for i in range(start, len(s)):
            # 현재 위치와 i 위치 문자를 바꿔가며 순열 생성
            s[start], s[i] = s[i], s[start]
            dfs(start + 1)  # 다음 위치로 이동
            s[start], s[i] = s[i], s[start]  # 백트래킹: 원상복구

    dfs(0)
    return results

# 예시 출력
print(permute("abc"))

3. 완전탐색의 시간복잡도 및 한계

  • 완전탐색은 보통 모든 경우를 탐색하므로 경우의 수에 따라 시간이 급격히 증가합니다.
  • 예를 들어, N-Queen 문제는 최악의 경우 O(N!) 정도 복잡도가 됩니다.
  • 부분집합 문제는 2^n, 순열 문제도 n! 복잡도를 가집니다.
  • 입력 크기가 작을 때 완전탐색은 강력하지만, 큰 입력에 대해서는 백트래킹, DP, 분할 정복 등으로 최적화해야 합니다.
728x90