Coding/알고리즘

백트래킹, 이진탐색

kangplay 2024. 12. 30. 17:35

백트래킹

백트래킹은 필요 없는 경우를 가지치기(pruning)함으로써 시간복잡도를 줄이는 방법이다.

바로 문제로 살펴보자.

N-Queen 

문제

 N-퀸 퍼즐은 두 여왕이 서로 공격하지 않도록 n x n 체스판 위에 n개의 여왕을 배치하는 문제입니다.
여왕은 상하좌우 대각선으로 원하는만큼 움직일 수 있습니다.

정수 n이 주어졌을 때, N-퀸 퍼즐에 대한 모든 고유한 해를 반환합니다. 어떤 순서로든 답을 반환할 수 있습니다.
각 해는 n개의 여왕 배치에 대한 고유한 보드 구성을 포함하며, 여기서 'Q'와 '.'는 각각 여왕과 빈 칸을 나타냅니다.

설명

모든 행과 열에는 하나의 퀸만 놓일 수 있고, 대각선 조건에서도 단 하나만 놓여야한다.
r행 c열에 퀸을 놓았다면, checked[r] = c에 저장하여 checked라는 리스트에는 퀸들의 위치를 저장하도록 하자.

재귀함수로 r+1을 넘겨주면서, (이미 행은 확인한 셈) 열과 대각선을 체크해주면서 checked 리스트를 채워주면 된다.

만약 is_ok 함수에서 False를 반환한다면, 그 다음의 경우들에 대해서는 살펴볼 필요도 없기 때문에 효율적인 방법이 된다.

구현
def nQueen(n):
    # n은 시작할 열의 위치이다.
    # checked 인덱스는 행, 값은 열을 나타낸다.
    # 2차원 배열로도 나타낼 수 있지만, 
    # 한 행에 하나의 값만 들어갈 수 있기 때문에 1차원 배열로 나타낼 수 있는 특수한 경우이다.

    checked = [-1] * n
    cnt = 0

    def is_ok(nth_row):
        for row in range(nth_row):
            # 열 번호가 겹치는지는 않은지
            # 또는 대각선으로 존재하지는 않은지 체크한다.
            if checked[nth_row] == checked[row] or nth_row - row == abs(checked[nth_row] - checked[row]):
                return False
            return True

    def dfs(row):
        # row는 퀸을 놓을 행번호를 의미한다.
        # row는 n-1까지 가능하며, n이 되었다는 것은 n개의 퀸을 모두 올바른 위치에 두었다는 의미이다.
        if row >= n:
            # 0 ~ n-1 행에 퀸을 모두 하나씩 두었을 때 경우의 수를 1 증가시키고 재귀탐색을 종료한다.
            nonlocal cnt
            cnt+=1
            return
        
        for col in range(n):
            checked[row] = col
            if is_ok(row):
                dfs(row + 1)

이진탐색

이진탐색은 배열이 정렬되어있을 경우, 절반씩 줄여나가면서 탐색하는 기법이다.

오름차순으로 정렬된 정수 배열 nums와 정수 target이 주어졌을 때, nums에서 대상을 검색하는 함수를 작성한다.

함수는 대상이 존재하면 그 색인을 반환하고 그렇지 않으면 -1을 반환한다.

def binary_search(nums, target):
    def bs(start,end):
        if start > end: return -1

        mid = (start + end) // 2

        if nums[mid] == target: return mid
        if nums[mid] > target: return bs(start, mid - 1)
        if nums[mid] < target: return bs(mid + 1, end) 

    return bs(0,len(nums)-1)

문제 - 순열 생성하기

문제
  • 주어진 정수 배열에서 모든 가능한 순열을 생성하는 프로그램을 작성하세요.
  • 배열에는 중복된 숫자가 없다고 가정합니다.
  • 백트래킹 알고리즘을 사용하여 효율적으로 모든 가능성을 탐색합니다.

설명

재귀함수를 통해서 숫자를 하나씩 배열에 추가하도록 한다. 이때, visited 배열을 참고하여 중복된 숫자를 넣지 않도록 한다.

  • len를 통해 숫자 배열이 N개가 되면 종료한다.
  • N개 이하인 경우에는 재귀함수를 다시 호출한다.
  • 재귀함수를 호출할 때에, nums를 복사하여 넘겨준다.
구현
from collections import deque

inputNums= list(map(int,input().replace('[','').replace(']','').split(',')))
queue= deque()
answers = []

def make_permutation(nums):
    if len(nums) == len(inputNums):
        answers.append(nums)
        return
    for num in inputNums:
        if num in nums:
            continue
        else:
            new_nums = nums.copy() 
            new_nums.append(num)
            make_permutation(new_nums)
    

for inputNum in inputNums:
    nums = []
    nums.append(inputNum)
    make_permutation(nums)

print("\n".join(map(str,answers)))