나동빈님 유튜브 내용을 참고 하였다.
개요
- 다이나믹 프로그래밍은 메모리를 적절히 사용하여 수행 시간 효율성을 비약적으로 향상시키는 방법
- 이미 계산된 결과(작은 문제)는 별도의 메모리 영역에 저장하여 다시 계산하지 않도록 한다.
- 구현 방식
- Top down
- Bottom up
특정 문제가 다음의 조건을 만족할 때 사용할 수 있다.
- 최적 부분 구조 (Optimal Substructure)
- 큰 문제를 작은 문제로 나눌 수 있으며, 작은 문제의 답을 모아서 큰 문제를 해결할 수 있다.
- 중복되는 부분 문제 (Overlapping Subproblem)
- 동일한 작은 문제를 반복적으로 해결해야 한다.
예제. 피보나치 수열
피보나치 수열은 다이나믹 프로그래밍으로 효과적으로 계산 가능
점화식으로 표현할 수 있다.
점화식을 재귀 함수로써 사용할 수 있음.
- 프로그래밍에서 이러한 수열을 배열이나 리스트를 이용해 표현 가능하다.
피보나치 수열이 계산되는 과정은 이진 트리 구조로써 구현가능 하다.
def fibo(x):
if x == 1 or x == 2:
return 1
return fibo(x-1) + fibo(x-2)
print(fibo(4))
# Answer: 3
단순 재귀 함수로 피보나치 수열을 해결하게 되면 지수 시간 복잡도를 가지게 된다.
예를 들어 fibo(6)을 구하기 위해서는 fibo(2)가 최소 5번 계산되게 된다. (중복되는 부분 문제)
또한 큰문제(fibo(6))을 구하기 위해 작은 문제(fibo(5) + fibo(4))로 나눌 수 있음 (최적 부분 구조)
메모이제이션 (Memoization)
메모이제이션은 다이나믹 프로그래밍을 구현하는 방법 중 하나로써 한 번 계산한 결과를 메모리 공간에 메모하는 기법입니다.
- 같은 문제를 다시 호출하면 메모했던 결과를 그대로 가져옴
- 값을 기록해 놓는다는 점에서 캐싱(cashing)이라고도 한다.
Top down vs Bottom up
탑다운(메모이제이션) 방식은 하향식이라고도 하며 바텀업 방식은 상향식이라고도 한다.
다이나밍 프로그래밍의 전형적인 형태는 바텀업 방식이고, 결과 저장용 리스트는 DP 테이블이라고 부른다.
엄밀히 말하자면, 메모이제이션은 이전에 계산된 결과를 일시적으로 기록해 놓는 넓은 개념을 의미한다. 따라서, 메모이제이션은 다이나밍 프로그래밍에 국한된 개념은 아니다.
- 한 번 계산된 결과를 담아 놓기만 하고, 다이나밍 프로그래밍을 위해 활용하지 않을 수도 있다.
- 다이나믹 프로그래밍 != 메모이제이션
Top down 방식 구현
# 한 번 계산된 결과를 메모이제이션 하기 위한 리스트 초기화
d = [0] * 100
# 피보나치 함수를 재귀함수로 구현(top down dynamic programming)
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]
print(fibo(99))
Bottom up 방식 구현
# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 100
# 첫 번째 피보나치 수와 두 번째 피보나치 수는 1
d[1] = 1
d[2] = 1
n = 99
# 피보나치 함수를 반복분으로 구현 (bottom up dynamic programming)
for i in range(3, n+1):
d[i] = d[i-1] + d[i-2]
print(d[n])
시간복잡도
메모이제이션을 사용하는 경우 피보나치 수열 함수의 시간 복잡도는 $$O(N)$$
다이나믹 프로그래밍 VS 분할 정복
다이나믹 프로그래밍과 분할 정복은 모두 최적 부분 구조를 가질 때 사용할 수 있다. 그러나, 이 둘의 차이점은 부분 문제의 중복이다.
- 다이나믹 프로그래밍: 각 부분 문제들이 서로 영향을 미치며 부분 문제가 중복됨
- 분할 정복: 동일한 부분 문제가 반복적으로 계산되지 않음
분할 정복 예시: 퀵 정렬
한 번 기준 원소( Pivot)가 자리를 변경해서 자리를 잡으면 그 기준 원소의 위치는 바뀌지 않는다. 분할 이후에 해당 피벗을 다시 처리하는 부분 문제는 호출하지 않는다.
다이나믹 프로그래밍 문제에 접근 하는 방법
주어진 문제가 다이나믹 프로그래밍 유형임을 파악하는 것이 중요하다. 가장 먼저 그리디, 구현, 완전탐색 등의 아이디어로 문제를 해결할 수 있는지 검토하고, 다른 알고리즘 풀이방법이 떠오르지 않는다면 다이나믹 프로그래밍을 고려해보자.
일단 재귀 함수로 비효율적인 완전탐색 프로그램을 작성한 뒤에 (탑다운) 작은 문제에서 구한 답이 큰 문제에서 그대로 사용될 수 있으면, 코드를 개선하는 방법을 사용할 수 있다.
일반적인 코딩 테스트 수준에서는 기본 유형의 다이나믹 프로그래밍 문제가 출제되는 경우가 많다 !
'CS > Algorithm' 카테고리의 다른 글
[Python] 소수 판별(IsPrime) 알고리즘 / 에라토스테네스의 체, 나누기 (0) | 2023.05.12 |
---|---|
10진수 n진수 변환, n진수 10진수 변환 (0) | 2023.05.10 |
우선순위 큐(Priority Queue)가 뭔데 ! (0) | 2023.04.12 |
댓글