본문 바로가기
ps/algorithm

DP

by choi-dev 2024. 8. 17.

Dynamic Programming의 줄임말로 동적 계획법이라고 한다. 

 

주어진 문제가 있으면 이를 여러 개의 하위 문제로 나누어 푼 다음, 그것을 결합하여 목적에 도달하는 방법이다. 하위 문제를 나누어 푼 답들을 미리 저장하고 같은 하위 문제가 나왔을 때, 또 계산을 하는 것이 아닌 저장한 하위 문제의 값을 사용해 계산 횟수를 줄인다.

 

해결책을 저장한 것을 메모이제이션(Memoization)이라고도 한다.

 

DP를 사용하는 경우

대부분의 경우에 사용할 수 있는 것은 아니다. DP를 사용하는 경우는 하위 문제가 있는지, 최적 부분 구조가 존재하는지로 확인할 수 있다. 

 

하위 문제가 있다는 것은 더 작은 부분으로 나눌 수 있고, 그 나눈 부분 일부가 재활용이 가능하다는 뜻이다. 다만, 각각의 부분들은 다른 모습이어서는 안된다.

 

피보나치 수열

피보나치 수열을 예시로 들어보자. 피보나치 수열은 간단히 정리해서 첫째 항과 둘째 항이 1이고 다음 항부터는 바로 뒤의 두 항의 합을 의미한다. 즉, 1 - 1 - 2 - 3 - 5 - 8이 된다. 좀 더 생각해보면 첫째 항의 값을 저장하고 둘째 항의 값을 저장하면 셋째 항의 값을 구할 수 있고 둘째 항, 셋째 항의 값을 저장하고 있으면 넷째 항의 값을 손쉽게 구할 수 있는 구조이다.

 

# f(n) = f(n-1) + f(n-2)

따라서 이러한 점화식을 구할 수 있다.

 

허나 피보나치 수열을 구사하는 방법은 DP말고도 재귀를 사용해서 해결할 수도 있다. 재귀에 대해서는 나중에 따로 설명하는 것으로 하고 일단 코드를 구해보면 다음과 같다.

 

def fib(n):
    if (n <= 2): return 1
    return fib(n - 1) + fib(n - 2)

print(fib(3))

이렇게 하면 피보나치 수열을 구할 수는 있으나 함수 안의 함수를 또 리턴하기에 숫자의 크기가 커질수록 계속해서 연산을 해나가야 하는 구조이기 때문에 좋지 않은 방식이다.

 

def dp(n):
    array = [0] * (n + 1)
    array[0] = 1
    array[1] = 1

    for i in range(2, n + 1):
        array[i] = array[i - 1] + array[i - 2]
    
    return array[n - 1]

print(dp(6))

이렇게 값을 값을 메모이제이션하여 필요할 때 꺼내쓰면 연산이 줄어들기에 O(n)이라는 시간 복잡도가 발생하게 되어 더 빨라진다. 참고로 위의 방식은 bottom-up 방식이다.

'ps > algorithm' 카테고리의 다른 글

DP - new  (0) 2025.01.18