Algorithm

다이나믹 프로그래밍 (동적 계획법)

yuull 2023. 4. 6. 17:53

다이나믹 프로그래밍의 목적

: 중복되는 연산을 줄이자


피보나치 수열처럼 특정 패턴을 반복하며 수열의 항이 이어지는 경우 '점화식'을 통해 이를 간결히 표현할 수 있다.

a(n) = a(n-1)+a(n-2), a1 = 1, a2 = 1

위처럼 인접한 항들의 관계식을 표현한 것을 '점화식'이라 한다.

 

위를 컴퓨터로 연산하려면 어떻게 해야 할까?

먼저 재귀함수로 연산하는 방법이 있다.

def fibo(x):
    if x == 1 or x == 2:
    	return 1
    return fibo(x-1) + fibo(x-2)

다만 이 경우 동일한 함수가 반복적으로 호출되어 수행 시간이 기하급수적으로 늘어난다는 문제가 있다.

시간 복잡도: O(2^N)


이를 해결하는 방법으로 다이나믹 프로그래밍 (Dynamic Programming)을 사용한다.

다이나믹 프로그래밍은

   1. 큰 문제를 작은 문제로 나눌 수 있다.

   2. 작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일하다.

위 두 조건을 만족해야 사용 가능하다.

 

다이나믹 프로그래밍 소스코드를 작성하는 방법

탑다운 Top-Down 방식: 재귀함수 이용, 메모이제이션

보텀업 Bottom-Up 방식: 반복문 이용, DP테이블

* DP테이블: 결과 저장용 테이블

 

Top-Down 방식

다이나믹 프로그래밍을 구현하는 방법 중 하나로 메모이제이션이 있다. 탑다운 방식이라 한다.

메모이제이션은 한 번 구한 결과를 모두 메모리 공간에 메모해두고 같은 식을 다시 호출하면 메모해놓은 결과를 가져오는 기법이다.

구현하는 방법으로는 한 번 구한 정보를 리스트에 저장하는 것이다. 같은 정보가 필요하게 되면 리스트에서 그 정보를 가져온다.

이렇게 다이나믹 프로그래밍을 적용하면 피보나치 수열 알고리즘의 시간 복잡도는 O(N)이 된다.

왜냐하면 f(1)을 구한 값이 f(2)를 푸는 데 사용되고, f(2)을 구한 값이 f(3)을 푸는 식으로 이어지기 때문에 한 번 구한 결과는 또 구하지 않기 때문이다.

탑다운 방식은 재귀함수를 이용한다.

# 메모이제이션하기 위한 리스트 초기화
d = [0] * 100

# 탑다운 다이나믹 프로그래밍 (재귀함수)
def fibo(x):
    # 종료 조건
    if x == 1 or x == 2:
    	return 1
    # 메모된 결과가 있다면 그걸 사용
    if d[x] != 0:
    	return d[x]
    # 없다면 점화식에 따른 결과 메모 후 반환
    d[x] = fibo(x-1) + fibo(x-2)
    return d[x]

 

Bottom-Up 방식

반면 보텀업 방식은 탑다운과 달리 작은 문제에서부터 시작하여 차근차근 답을 도출하는 방식이다.

탑다운과 원리는 동일하되 반복문을 사용한 상향식 방식이라는 것이 차이점이다.

역시 한 번 구한 결과는 다시 구하지 않기 때문에 보텀업으로 구현한 피보나치 수열 알고리즘의 시간복잡도 또한 O(N)이다.

반복문을 사용하며, 계산한 결과 값은 DP 테이블이라는 결과 저장용 리스트에 저장한다.

# DP테이블 초기화
d = [0] * 100

d[1] = 1
d[2] = 1
n = 99

# 보텀업 다이나믹 프로그래밍 (반복문)
for i in range(3, n+1):
    d[i] = d[i-1] + d[i-2]

 

* 탑다운 방식(재귀함수)보다는 보텀업 방식(반복문)을 권장한다.

재귀함수를 사용하면 컴퓨터시스템에서는 함수를 다시 호출했을 때 메모리 상에 적재되는 일련의 과정을 따라야 한다.

이럴 경우 시스템상 재귀함수의 스택 크기가 한정되어 있을 수 있어 오버헤드가 발생해 'recursion depth'와 관련된 오류가 발생할 수 있다. (이 때 sys라이브러리에 포함돼있는 setrecursionlimit() 함수로 재귀 제한을 완화할 수 있다.)

따라서 반복해서 함수를 호출하는 방식은 비효율적이고, 가능하면 보텀업 방식을 사용하는 것이 좋다.