티스토리 뷰
이 챕터에서는 Dynamic Programming이라는 문제 해결 방법에 대해 배운다.
Dynamic Programming은 어떤 문제를 해결할 때 sub문제들의 subsub문제가 overlap되는 경우 사용하는 Algorithm기법을 말한다. 책에서는 이 예시로 Rod Cutting문제를 말하고 있는데 Rod Cutting문제란 다음과 같은 문제를 말한다.
Q: 어떤 Value를 지닌 통나무(Rod)를 판다고 해보자. 그런데 이 통나무는 길이에 따라 매기는 값이 비례하지 않게 달라진다. 예를 들어 다음 표와 같이 길이에 따른 값이 달라진다.
이 때 주어진 길이 n의 통나무와 값이 담겨있는 배열 p에 대해 이 Rod의 max Price는?
Input: 통나무의 길이n, 값이 담긴 배열 p[1:n]
Output: 길이에 따른 Max Price가 담긴 배열 r[1:n]
일단, 가장 Naive하게 생각해보자. 길이 10인 통나무가 있다고 해보면 이 통나무를 자를 수 있는 방법은 총 몇개일까? 고등학교 확률과 통계 자연수의 분할을 생각해보면 10을 1개로, 2개로, 3개로, ..., 10개로 분할하는 모든 경우의 수의 합이겠지만, 가장 Naive하게 생각해보면 29가지가 될 것이다. 왜냐하면 1이 10개 붙어있으면 총 9개의 자를 수 있는 틈이 있기 때문에 각 9개의 지점에서 자를 지 말지 결정하면 되기 때문이다.(가장 Naive한 생각이다. 자연수의 분할을 적용하면 이것보다 가지수가 더 적게 나올 것이다. 왜냐하면 중복되는 경우가 많이 나오기 때문. 이건 뒤에서 다루자) 그럼 이 Naive한 구현은 어떻게 가능하겠는가? 우선 길이가 n인 rod를 봤을 때 최댓값은 다음 중 하나일 것이다. rn=max(pn,ri+rn-i)(i is in [1:n-1])
즉, max Price는 그냥 안 잘랐을 때나, 어딘가 최소한 하나를 자른 경우 두 토막의 최댓값을 합한 값일 것이다.
이 코드는 다음과 같이 구현할 수 있다.
int rodcuttingNaive(int n, int* price,int* r) {
if (n <1) { return 0; }
int temp = price[n];
for (int i = 1; i < n; i++) {
if (temp < rodcuttingNaive(i, price, r) + rodcuttingNaive(n - i, price, r))
temp = rodcuttingNaive(i, price, r) + rodcuttingNaive(n - i, price, r);
}
r[n] = temp;
return temp;
}
(solution은 r에 담는다.)
이렇게 연산하게 되면 O(n)=3n의 복잡도를 갖게 된다.(복잡도 분석은 생략)
근데, 이런 연산을 조금 줄일 수 있는데, 자연수의 분할 성질을 생각해보는 것이다. 만약, 하나라도 자르는 곳이 있는 게 해라면, 왼쪽에는 무조건 하나의 잘리지 않은 통나무가 남게 될 것이다. 따라서
rn=max(pi+rn-i)(i is in [0:n-1]),단 p0=0
이렇게 연산하게 되면 O(n)=2n의 복잡도를 갖게 된다.
그런데 이렇게 되면 다음과 같이 연산을 하는 꼴이나 마찬가지다.
즉, 동일한 subproblem을 너무 여러번 풀게 된다. 따라서 Dynamic Programming은 다음과 같은 방법을 제시한다.
만약 이미 푼 문제라면 그냥 해를 제시하고 풀지 않은 문제일 때만 함수를 call해라
예를 들어, 저 위쪽의 problem tree에서 4-3으로 이어지는 subtree를 모두 풀었다면 오른쪽의 problem을 풀 때는 그냥 저장된 값을 주면 된다. 그런데 이런 Top-down방식으로 문제를 구현하면 if문이 하나 필요해지니까 그렇게 하지말고 그냥 Bottom-UP으로 구현하면 더 간단하게 된다. 이 Bottom-UP방식의 코드와 TestCode까지 전부 밑에 제시한다.
#include<iostream>
using namespace std;
const int INFINITE = 100000000000;
int rodcuttingNaive(int n, int* price,int* r) {
if (n <1) { return 0; }
int temp = price[n];
for (int i = 1; i < n; i++) {
if (temp < rodcuttingNaive(i, price, r) + rodcuttingNaive(n - i, price, r))
temp = rodcuttingNaive(i, price, r) + rodcuttingNaive(n - i, price, r);
}
r[n] = temp;
return temp;
}
int* BottomUpFashion(int n, int* price) {
int* r = new int[n + 1];
r[0] = 0; //too obvious
int temp;
for (int i = 1; i <= n; i++) {
temp = -INFINITE;
for (int j = 0; j <= i; j++) {
if (temp < price[j] + r[i - j]) temp = price[j] + r[i - j];
}
r[i] = temp;
}
return r;
}
int main() {
int p[] = {0, 1,5,8,9,10,17,17,20,24,30 };
int* r = new int[11];
//(1) Test Naive Solution
rodcuttingNaive(10, p, r);
for (int i = 1; i < 11; i++) {
cout << "r[" << i << "]: " << r[i] << endl;
}
//(2) Test Bottom Up Fashion
int* k = BottomUpFashion(10, p);
for (int i = 1; i < 11; i++) {
cout << "k[" << i << "]: " << k[i] << endl;
}
delete k;
}
이렇게 하면 최대 n번까지 돌아가는 for문이 2개니까 n2까지 수행시간이 줄어들게 된다.
즉 Dynamic Programming의 일반적인 방법론이 무엇인지는 다음과 같이 말해볼 수 있을 것 같다.
(1) Optimal Solution의 구조를 Characterize
(2) Optimal Solution의 value를 recursively define
(3) bottom-up fashion을 통해 optimal solution의 값을 계산
'알고리즘의 기초' 카테고리의 다른 글
4-(1)Dynamic Programming 3)Longest Common SubSequence (0) | 2020.02.09 |
---|---|
4-(1) Dynamic Programming 2)Matrix Chain Multiplication (0) | 2020.02.03 |
3-Elementary Data Structure (0) | 2020.02.01 |
2-(3) Sorting in Linear time-Counting Sort (0) | 2020.01.30 |
2-(2) QuickSort (0) | 2020.01.26 |
- Total
- Today
- Yesterday
- 머신 러닝
- 신경망
- 컴퓨터과학
- 영상처리
- 머신러닝
- 자연어 처리
- 딥러닝
- 밑바닥부터 시작하는 딥러닝
- 이미지처리
- NLP
- 매트랩 함수
- 컴퓨터 과학
- 순환 신경망
- 영상구조
- 연속 신호
- Andrew ng
- Logistic Regression
- rnn
- gradient descent
- 신호 및 시스템
- ML
- 인덱스 이미지
- CNN
- CS
- 사진구조
- 매트랩
- RGB이미지
- Neural Network
- 이산 신호
- 이미지
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |