-
[다이나믹 프로그래밍] 개념 정리Algorithm/개념정리 2021. 11. 23. 21:11
다이나믹 프로그래밍(동적 계획법)?!
- 메모리를 적절히 사용하여 수행 시간 효율성을 비약적으로 향상시키는 방법임
- 이미 계산된 결과(작은 문제)는 별도의 메모리 영역에 저장하여 다시 계산하지 않도록 함
- 구현은 일반적으로 2가지 방식(탑다운 & 보텀업)으로 구성됨
- '동적 계획법' 이라고도 부르는데,
- 흔히 프로그래밍에서 다이나믹은 '프로그램이 실행되는 도중에' 라는 의미임
- 예를 들어, 자료구조에서 동적 할당은 프로그램 실행 중에 프로그램 실행에 필요한 메모리를 할당하는 기법임
- 하지만 다이나믹 프로그래밍에서의 '다이나믹'은 동적 할당(Dynamic Allocation)에서의 다이나믹과는 다른 의미임
다이나믹 프로그래밍의 기본적인 아이디어
1줄 요약 : 다이나믹 프로그래밍이란 큰 문제를 작게 나누고, 같은 문제라면 한 번씩만 풀어 문제를 효율적으로 해결하는 알고리즘 기법
# 다이나믹 프로그래밍을 사용하기 위한 조건
- 주어진 문제가 다음의 조건을 만족할 때 사용할 수 있음
1. 최적 부분 구조 (Optimal Substructure)
→ 큰 문제를 작은 문제로 나눌 수 있으며 작은 문제의 답을 모아서 큰 문제를 해결할 수 있음
2. 중복되는 부분 문제 (Overlapping Subproblem)
→ 동일한 작은 문제를 반복적으로 해결해야 함
# 구현 방법
## 메모이제이션(Memoization) - 개념적 용어
- 메모이제이션은 다이나믹 프로그래밍을 구현할 때 사용하는 방법임(중복되는 부분 문제 해결을 위한 방법 중 하나)
- 한 번 계산한 결과를 메모리 공간에 메모하는 기법
- 같은 문제를 다시 호출하면 메모했던 결과를 그대로 가져옴
- 값을 기록해 놓는다는 점에서 캐싱(Caching)이라고도 함 (컴퓨터 구조 수업에서도 지금 Cache Memory에 대해 공부하고 있는데, 알고리즘에서도 캐싱 기법이 적용될 수 있다니..! 흥미롭다😲)
## 탑다운 VS. 보텀업
- 탑다운 방식은 하향식이라고도 하며, 보텀업 방식은 상향식이라고도 함
- 다이나믹 프로그래밍의 전형적인 형태는 보텀업 방식임
- 결과 저장용 리스트는 DP 테이블이라고 부름
- 엄밀히 말하면, 메모이제이션은 이전에 계산된 결과를 일시적으로 기록해 놓은 넓은 개념을 의미함
- 메모이제이션은 다이나믹 프로그래밍에 국한된 개념이 아님
- 한 번 계산된 결과를 담아 놓기만 하고 다이나믹 프로그래밍을 위해 활용하지 않을 수도 있음
## 탑다운(Top-Down) 방식
→ 재귀 함수를 이용하여 다이나믹 프로그래밍 소스코드를 작성하는 방법
→ 큰 문제를 해결하기 위해 작은 문제를 호출한다고 하여 탑다운 방식이라고 함 (하향식)
## 보텀업(Bottom-Up) 방식
→ 반복문을 이용하여 다이나믹 프로그래밍 소스코드를 작성하는 방법
→ 작은 문제부터 차근차근 답을 도출함 (상향식)
다이나믹 프로그래밍으로 해결할 수 있는 예시 - 피보나치 수열
# 기존 구현 방식
def fibo(x): if x == 1 or x == 2: return 1 return fibo(x-1) + fibo(x-2) print(fibo(50))
문제점 :
fibo(x) 함수에 x 가 커질수록 수행 시간이 기하급수적으로 늘어남
동일한 함수가 반복적으로 호출됨 → 이미 한 번 계산했지만, 계속 호출할 때마다 계산함
# 다이나믹 프로그래밍 적용 방식
구현1) 메모이제이션 기법을 사용한 피보나치 수열 코드 by 탑다운(Top-Down) 방식 - 재귀적
import java.util.*; public class fibonacci_with_memoization { // 한 번 계산된 결과를 메모이제이션(Memoization)하기 위한 배열 초기화 public static long[] result = new long[100]; // 피보나치 함수를 재귀함수로 구현 (with 탑다운 다이나믹 프로그래밍) public static long fibo(int x){ if(x == 1 || x == 2){ // 재귀 종료 조건 return 1; } if(result[x] != 0){ // 이미 계산한 적이 있는 값이라면 배열의 값 그대로 반환 return result[x]; } result[x] = fibo(x-1) + fibo(x-2); // 계산한 적이 없는 겂이라면 점화식에 따라 피보나치 결과 반환 return result[x]; } public static void main(String[] args){ System.out.println(fibo(50)); } }
→ 재귀적으로 수행하다가 같은 정보가 필요할 때는 이미 구한 정답을 그대로 리스트에서 가져오면 됨
구현2) 메모이제이션 기법을 사용한 피보나치 수열 코드 by 보텀업(Bottom-Up) 방식 - 반복적
import java.util.*; public class fibonacci_with_memoization_iterative { public static long[] result = new long[1000000]; public static void main(String[] args){ // 첫 번째와 두 번째 값은 1로 고정 result[1] = 1; result[2] = 1; int val = 100; // 찾고자 하는 값 // 피보나치 함수를 반복문으로 구현(다이나믹 프로그래밍 보텀업 방식) for(int i = 3; i <= val; i++){ result[i] = result[i-1] + result[i-2]; } System.out.println(result[val]); } }
→ 시간 복잡도 : O(N)
→ 이유 : f(1)을 구한 다음 그 값이 f(2)를 푸는데 사용되고, f(2)의 값이 f(3)을 푸는데 사용되는 방식으로 이어짐. 즉 한 번 구한 결과는 다시 구해지지 않음
다이나믹 프로그래밍 VS. 분할 정복
- 모두 최적 부분 구조를 가질 때 사용 가능함
- 큰 문제를 작은 문제로 나눌 수 있으며 작은 문제의 답을 모아서 큰 문제를 해결할 수 있는 상황
- 차이점은 부분 문제의 중복
- 다이나믹 프로그래밍 문제에서는 각 부분 문제들이 서로 영향을 미치며 부분 문제가 중복됨
- 분할 정복 문제에서는 동일한 부분 문제가 반복적으로 계산되지 않음
다이나믹 프로그래밍 문제에 접근하는 방법
# 주어진 문제가 다이나믹 프로그래밍 유형인지 파악하기
- 주어진 문제가 다이나믹 프로그래밍 유형임을 파악하는 것이 중요함
- 가장 먼저 그리디, 구현, 완전 탐색 등의 아이디어로 문제를 해결할 수 있는지 검토할 수 있음
- 다른 알고리즘으로 풀이 방법이 떠오르지 않으면 다이나믹 프로그래밍을 고려해 보자!
- 특정한 문제를 완전 탐색 알고리즘으로 접근했을 때 시간이 매우 오래 걸리면 다이나믹 프로그래밍을 적용할 수 있는지 즉 해결하고자 하는 부분 문제들의 중복 여부를 확인해봐야 함
# 비효율적인 코드 개선하기
- 일단 재귀 함수로 비효율적인 완전 탐색 프로그램을 작성한 뒤에 (탑다운) 작은 문제에서 구한 답이 큰 문제에서 그대로 사용될 수 있으면, 코드를 개선하는 방법을 사용할 수 있음
- 가능하다면 재귀 함수로 먼저 구현하기보다 보텀업 방식(반복문)으로 구현하는 것이 좋음 (이유: 시스템상 재귀 함수의 스택 크기가 한정되어 있을 수 있음)
- 일반적인 코딩 테스트 수준에서는 기본 유형의 다이나믹 프로그래밍 문제가 출제되는 경우가 많음
참고) 이것이 취업을 위한 코딩 테스트다 with 파이썬
'Algorithm > 개념정리' 카테고리의 다른 글
[그리디] 개념 정리 (0) 2022.02.07 [최단 경로] 개념 정리 (0) 2022.01.11 [이진 탐색] 개념정리 (0) 2021.11.18 [정렬] 개념정리 글 모음 - List<T>, 사용자 정의 객체, 배열 (0) 2021.09.30 [정렬] 개념정리1 - 삽입 정렬, 선택 정렬, 퀵 정렬, 계수 정렬 (0) 2021.08.18