동적 계획법


동적계획법(Dynamic Programming)?

문제의 최적해를 구하거나 답의 개수를 세는 과정에 사용할 수 있는 알고리즘 설계 기법.

동적 계획법을 사용하면 불필요한 계산을 줄이고, 효율적으로 최적해를 찾을 수 있다.

전체 문제를 작은 문제로 단순화한 다음 점화식을 만들어 재귀적인 구조를 활용해서 전체 문제를 해결하는 방식이다.

처음 주어진 문제를 더 작은 문제들로 나눈 뒤 작은 문제들의 답을 구한 후 원래 문제에 대한 답을 계산한다는 점에서 분할 정복과 비슷하다.

하지만 동적 계획법에서는 작은 문제들이 중복될 수 있지만, 분할 정복은 중복될 수 없다는 점이다.

다시 말해 동적 계획법과 분할 정복의 차이는 문제를 나누는 방식이다. 동적 계획법에서 어떤 작은 문제는 두 개 이상의 문제를 푸는데 사용될 수 있기 때문에, 이 문제의 답을 여러 번 계산하는 대신 한 번만 계산하고 그 결과를 저장하여 재활용해서 속도를 향상시킬 수 있다. 이때 이미 계산한 값을 저장해 두는 메모리를 캐시라고 하며, 두 번 이상 계산되는 작은 문제를 중복되는 부분 문제(overlapping subproblems)라고 부른다.

  1. 부분 문제를 정의
  2. 점화식 생성
  3. 해결

메모이제이션(Memoization)?

메모이제이션은 동적계획법에서 중요한 개념으로, 이미 계산된 값을 저장하여 필요할 때마다 값을 다시 계산하지 않고 저장된 값을 가져올 수 있다.

조건

두 가지를 만족해야 동적 계획법으로 문제를 풀 수 있다.

  1. Overlapping subproblem: 겹치는 부분 문제
  2. Optimal Substructure : 최적 부분 구조

1. Overlapping Subproblem

겹치는 부분 문제는 어떤 문제가 여러개의 부분문제로 쪼개질 수 있을 때 사용하는 단어이다. 이 부분 문제는 새로운 부분 문제를 생성하기보다는 여러번 재사용되거나 재귀 알고리즘을 통해 해결되는 문제를 말한다.

피보나치 수열이 위에 해당한다. (앞의 두 수를 더하면 다음 수가 된다)

이때 문제와 작은 문제는 다음과 같이 될 것이다.

  • 문제 : N번째 피보나치 수를 구하는 문제
  • 작은 문제 : N-1번째 피보나치 수를 구하는 문제, N-2번째 피보나치 수를 구하는 문제

2. Optimal Substructure

최적 부분구조는 어떤 문제의 최적의 해결책이 그 부분 문제의 최적의 해결책으로부터 설계될 수 있는 경우를 말한다.

즉 최적 부분구조일 때 문제의 정답을 작은 문제의 정답에서부터 구할 수 있다.

피보나치를 다시 보자.

10 번째 피보나치 수를 구할 때는 예를 들어 2번째 피보나치 수를 이용해야 한다. 9 번째도 마찬가지이고, 2번째 피보나치 수를 여러번 이용해야 한다. 하지만 매번 2번째 피보나치 수를 매번 계산하는 것은 비효율적이다.

이를 해결할 수 있는 방법이 위에서 언급했던 메모이제이션이다.

메모이제이션

동적 계획법에서 각 문제는 한 번만 풀어야 한다. Optimal Substructure를 만족하기 때문에 같은 문제는 구할 떄마다 정답이 가타. 따라서 정답을 한 번 구했으면 그 정답을 캐시에 메모해놓는다.

이것을 코드로 구현할 때는 배열에 저장하는 것으로 할 수 있다.

동적 계획법 구현 방식

  1. Top-down : 큰 문제를 작은 문제로 쪼개면서 푼다. 재귀로 구현
  2. Bottom-up : 작은 문제부터 차례대로 푼다. 반복문으로 구현

python의 경우 재귀 호출 시 스택 오버플로우가 발생할 수 있기 때문에 Bottom-up으로 구현하는 것이 좋다.

참고 : https://velog.io/@polynomeer/%EB%8F%99%EC%A0%81-%EA%B3%84%ED%9A%8D%EB%B2%95Dynamic-Programming