[Algorithm] Dynamic Programming, 동적 계획법

Kozel·2023년 3월 16일
1
post-thumbnail

다이나믹 프로그래밍이란?

  • 큰 문제를 작은 문제로 나눠서 해결하는 알고리즘이다.

    분할 정복: 작은 문제의 중복이 없다.
    다이나믹 프로그래밍: 작은 문제의 중복이 있다.

  • 위의 분할 정복과의 차이점에서 보이듯이 작은 문제의 중복이 있을 때 사용한다.

다이나믹으로 해결할 수 있는 문제

  1. 겹치는 부분 문제가 있는 경우 (Overlapping Subproblem)
    • 큰 문제를 작은 문제로 쪼갤 수 있는 경우
  2. 최적화 부분 구조 (Optimal Substructure)
    • 작은 문제를 풀어나감으로써 큰 문제의 정답을 구할 수 있는 경우

다이나믹 프로그래밍 최적화의 핵심

  • 작은 문제의 답은 한번만 구해야 한다.
    • 같은 풀이를 반복해선 안된다.
  • 최적화 부분 구조를 만족하기 때문에, 같은 문제를 구할 때마다 정답이 같다.
  • 구한 답은 버리지 않고 저장해 둔다.
    • 해당 과정을 Memoization이라고 한다.
    • ex) 배열


다이나믹 프로그래밍의 구현 방식

다이나믹 프로그래밍의 대표적인 예인 피보나치 수를 구하는 경우로 두가지 방식을 살펴보자.

Top-Down

  • 위에서 아래로 내려오는 방식으로, 원하는 값을 먼저 부른 다음에 그 값을 이루는 작은 값들을 이용해 값들을 수집해 나가는 방식이다.
  • 메모제이션과 재귀함수를 사용하여 구현한다.
  • javascript code

    // question: 구하려는 수의 값
    
    const arr = Array(question + 1).fill(0);
    
    const fibonacci = (number) => {
        if(number === 0) return 0;
        if(number === 1) return 1;
        if(arr[number] !== 0) return arr[number];
    
        arr[number] = fibonacci(number - 1) + fibonacci(number - 1);
    
        return arr[number];
    }
    
    console.log(fibonacci(question));
  • java code

    // question: 구하려는 수의 값
    
    int[] arr = new int[question + 1];
    
    System.out.print(fibonacci(question));
    
    static int fibonacci(int number) {
        if(number == 0) return 0;
        if(number == 1) return 1;
        if(arr[number] != 0) return arr[number];
    
        arr[number] = fibonacci(number - 1) + fibonacci(number - 1);
    
        return arr[number];
    }

Bottom-Up

  • 아래에서 올라오는 방식으로, 작은 문제부터 계산하여 전체 큰 값까지 구해내는 방식이다.
  • 메모제이션과 반복문을 사용하여 구현한다.
  • javascript code

    // question: 구하려는 수의 값
    
    const arr = [0, 1];
    
    for(let i = 2; i < question + 1; i++) {
        arr.push(arr[i - 1] + arr[i - 2]);
    }
    
    console.log(arr[question]);
  • java code

    // question: 구하려는 수의 값
    
    ArrayList<Integer> arr = new ArrayList<>();
    arr.add(0);
    arr.add(1);
    
    for(int i = 2; i < question + 1; i++) {
        arr.add(arr.get(i - 1) + arr.get(i - 2));
    }
    
    System.out.print(arr.get(question));

Top-down, Bottom-up 중 어떤걸 사용해야 할까?

결론부터 말하면 본인이 편한걸 사용하자.

구조적인 관점으로 봤을 때는 Top-down을 사용했을 경우 재귀 덕분에 Stack Overflow가 발생할 수 있고 반복문을 사용하는 Bottom-up이 더 빠르다고 볼 수 있지만 어떤게 더 좋다고 확실하게 말 할 수 없다.

Stack Overflow가 발생할 경우 다이나믹 프로그래밍을 잘못 구현했을 확률이 높고 Top-down은 모든 문제를 풀어보지 않아도 답이 나오는 경우가 있기 때문이다.

정리하자면 문제를 잘 파악하고 적합한 방식을 적용하는 것이 좋겠지만 어렵게 생각하지 말고 둘 중 하나를 정확하게 사용하는 것이 가장 중요하다.

더군다나 Top-down과 Bottom-up은 대부분의 경우에 서로 대체 가능하다.


다이나믹 프로그래밍 문제는 어떤식으로 풀이해야 할까?

  1. Top-down, Bottom-up 중 어떤 방식으로 접근할 것인지 결정한다.
  2. 점화식의 정의를 세운다.
    • 수식으로 점화식을 구하는 방식이 있고 표 또는 그래프를 통해 점화식을 구하는 방식이 있다. 편한걸로 하자.
  3. 구한 점화식을 통해 구현한다.


결론

다이나믹 프로그래밍을 많이 풀어본 것은 아니지만 풀 때마다 새롭게 느껴진다.

문제마다 구해야 하는 점화식이 다르고 해당 점화식을 파악하기가 쉽지 않기 때문에 더욱 그렇게 느껴지는 것 같다.

해야할 것은 작은 문제로 나누는 것을 이해하는 것과 당연하게도 많이 풀어보는 것이다.

profile
front-end developer

1개의 댓글

comment-user-thumbnail
2023년 3월 27일

오맛 멋져요~

답글 달기