ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [다이나믹 프로그래밍] 개념 정리
    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 파이썬

     

     

     

    댓글

Designed by Tistory.