
재귀적 해법: 관계중심으로 파악해서 문제를 간명하게 볼 수 있지만, 심한 중복 호출이 일어나는 경우가 있다. (ex. 피보나치수를 구하는 재귀 알고리즘, 행렬 곱셈 최적순서 구하기)
-> 피보나치 알고리즘의 경우, Divid & Conquer(D&Q) 알고리즘 방법으로 설게하면 같은 항을 한 번 이상 계산하는 결과를 초래하게 되어 효율적이지 않다.
이 해결책이 동적 프로그래밍이다.
먼저 입력 크기가 작은 부분 문제들을 모두 해결한 후 그 해들을 이용하여 보다 큰 크기의 부분 문제들을 해결한다. => 최종적으로 원래 주어진 입력의 문제를 해결
-> Bottom-up algorithm (D&Q는 top-down)
fibonacci(n)
{
f[0] = 0; f[1] = 1;
for(i = 2; i <= n; i++)
f[i] = f[i-1]+f[i-2];
return f[n];
}
-> 선형시간에 끝난다.
동적 프로그래밍을 적용하려면 최적성 원칙(principle of optimality) 또는 최적 부분구조(optimal substructure) 특성을 가지고 있어야만 함
=> 큰 문제의 최적 해에 작은 문제의 최적 해가 포함됨
한 정점에서 다른 정점으로의 모든 경로의 길이를 구한 뒤, 그들 중에서 최소 길이를 찾는다.
절대적으로 비효율적임
자료구조: 그래프의 인접행렬식 표현
D(k)[i][j]: 정점들 {v1, v2, ... , vk}을 통해서, 정점 i에서 정점 j로 가는 최단경로의 길이
동적계획 알고리즘으로 모든 쌍의 최단 경로 문제를 해결하려면 먼저 부분 문제들을 찾아야 함
step 1: D(k)[i][j] = min(D(k-1)[i][j], D(k-1)[i][k] + D(k-1)[k][j])
-> 'vi에서 vj로 가는 최단 경로가 vk를 거치지 않는 경우'와 'vk를 거치는 경우'로 나눈다.
step 2: 상향식으로 k=1부터 n까지 반복하여 해를 구한다.

시간복잡도: O(n^3)
Floyd-Warshall algorithm
void floyd(int n, const number W[][], number D[][]){
int i, j, k;
D = W;
for(k = 1; k <= n; k++)
for(i = 1, i <= n; i++)
for(j = 1; j <= n; j++)
D[i][j] = minimum(D[i][j], D[i][k] + D[k][j]);
// 계속 노드와 노드사이를 갈 때, 다른 노드를 거치는 것과 거치지 않는 것으로 구분
}
| Greedy 방법 | Dynamic Programming 방법 |
|---|---|
| 최적화 문제를 푸는데 적합 | 최적화 문제를 푸는 데 적합 |
| 알고리즘이 존재할 경우 보통 더 효율적 | 때로는 불필요하게 복잡 |
| 알고리즘이 최적인지 증명해야 함 | 최적화 원칙이 적용되는지를 점검해 보기만 하면 됨 |
| 단일 출발점 최단경로 문제: O(n^2) | 단일 출발점 최단경로 문제 : O(n^2) |
-problem-
S = {아이템1, 아이템2,...}
wi = 아이템i의 무게
pi = 아이템i의 가치(이윤)
W = 배낭에 넣을 수 있는 총 무게라고 할 때,
배낭에 들어간 총 무게 <= W를 만족하면서, 배낭에 들어간 총 이윤이 최대가 되도록 하는 답을 찾는 문제
n개의 물건에 대해서 모든 부분집합을 다 고려한다.
크기가 n인 집합의 부분집합의 수는 2^n개이다.
1) 가장 비싼 물건부터 우선적으로 채운다 : 최적 보장 안됨
2) 무게 당 가치(이윤)이 가장 높은 물건부터 우선적으로 채운다 : 최적 보장 안됨
<복습> The Fractional Knapsack Problem

