동적 프로그래밍(Dynamic Programming)은 복잡한 문제를 간단한 하위 문제로 나누어 풀고, 그 결과를 저장하여 재활용하는 기법입니다. 이를 통해 문제의 시간복잡도를 크게 줄일 수 있습니다.
동적 프로그래밍은 보통 다음과 같은 단계로 진행됩니다:
- 큰 문제를 작은 하위 문제들로 나눕니다.
- 작은 하위 문제들을 푸는 방법을 찾습니다.
- 작은 하위 문제들을 푼 결과를 이용하여 큰 문제를 해결합니다.
이번 포스트에서는 동적 프로그래밍을 활용하여 효율적으로 문제를 해결하는 방법과 그에 대한 예제를 소개하겠습니다.
1. 피보나치 수열 계산하기
피보나치 수열은 이전 두 개의 수를 더한 값을 다음에 추가하는 형태로 이루어집니다. 예를 들어, 처음 두 숫자는 0과 1이며, 그 다음 숫자는 1입니다. 이 과정을 반복하면 다음과 같은 수열이 생성됩니다: 0, 1, 1, 2, 3, 5, 8, …
우리의 목표는 피보나치 수열의 n번째 숫자를 계산하는 것입니다.
일반적인 방법으로는 재귀를 이용하여 피보나치 함수를 구현할 수 있습니다. 하지만 이 방법은 중복 계산이 발생하여 비효율적입니다.
동적 프로그래밍을 활용하여 중복 계산을 피하고 피보나치 수열을 효율적으로 계산하는 코드를 살펴보겠습니다.
def fibonacci(n):
# 기저 사례: n이 0 또는 1인 경우
if n <= 1:
return n
# 이미 계산한 값이 있는 경우, 바로 반환
if memo[n] is not None:
return memo[n]
# 계산한 값이 없는 경우, 피보나치 수열 계산
memo[n] = fibonacci(n-1) + fibonacci(n-2)
return memo[n]
# 피보나치 수열의 10번째 숫자 출력
n = 10
memo = [None] * (n+1) # 계산한 값을 저장하기 위한 메모리
print(fibonacci(n))
위 코드에서는 memo
라는 리스트를 이용하여 이미 계산한 값을 저장합니다. 이를 통해 중복 계산을 피하고 피보나치 수열을 효율적으로 계산할 수 있습니다.
2. 배낭 문제 해결하기
배낭 문제는 무게와 가치가 있는 아이템들을 배낭에 담을 때, 배낭에 담을 수 있는 아이템의 최대 가치를 구하는 문제입니다.
예를 들어, 배낭의 최대 용량이 10이고, 아이템들이 다음과 같이 주어졌다고 가정해봅시다:
아이템 | 무게 | 가치 |
---|---|---|
1 | 3 | 100 |
2 | 5 | 250 |
3 | 2 | 150 |
4 | 1 | 50 |
이 경우, 배낭에 담을 수 있는 아이템의 최대 가치는 300입니다.
이 문제를 동적 프로그래밍을 활용하여 효율적으로 풀 수 있는데, 다음과 같은 방법을 사용할 수 있습니다.
- 배낭의 크기에 따라 가능한 모든 조합을 확인합니다.
- 각 아이템을 추가할 때, 해당 아이템이 배낭의 용량을 초과하지 않는 경우에만 가치를 누적하여 저장합니다.
- 최종적으로 저장된 값이 배낭에 담을 수 있는 아이템의 최대 가치입니다.
def knapsack(capacity, weights, values, n):
# 기저 사례: 배낭의 크기가 0일 때
if capacity == 0 or n == 0:
return 0
# 이미 계산한 값이 있는 경우, 바로 반환
if memo[n][capacity] is not None:
return memo[n][capacity]
# 아이템을 추가하지 않는 경우
if weights[n-1] > capacity:
memo[n][capacity] = knapsack(capacity, weights, values, n-1)
else:
# 아이템을 추가하는 경우와 추가하지 않는 경우 중 더 큰 값을 저장
memo[n][capacity] = max(
values[n-1] + knapsack(capacity-weights[n-1], weights, values, n-1),
knapsack(capacity, weights, values, n-1)
)
return memo[n][capacity]
# 배낭의 최대 용량과 아이템들의 정보
capacity = 10
weights = [3, 5, 2, 1]
values = [100, 250, 150, 50]
n = len(weights)
memo = [[None for _ in range(capacity+1)] for _ in range(n+1)] # 계산한 값을 저장하기 위한 메모리
print(knapsack(capacity, weights, values, n))
위 코드에서는 memo
라는 2차원 리스트를 이용하여 이미 계산한 값을 저장합니다. 이를 통해 중복 계산을 피하고 배낭 문제를 효율적으로 해결할 수 있습니다.
결론
동적 프로그래밍은 복잡한 문제를 간단한 하위 문제로 분해하여 풀고, 그 결과를 저장하여 재활용하는 기법입니다. 예제로 피보나치 수열 계산과 배낭 문제를 효율적으로 해결하는 방법을 살펴보았습니다.
동적 프로그래밍은 다양한 문제에 적용될 수 있으며, 효율적인 문제 해결을 위한 강력한 방법론입니다. 사용하는 프로그래밍 언어에 상관없이 동적 프로그래밍을 활용하여 문제 해결 전략을 개발하고, 효율적인 알고리즘을 구현할 수 있습니다.