Dynamic Programming
Basic Problem
행렬 경로 문제
- 정수들이 저장된 N X N 행렬의 좌상단부터 우하단까지 이동한다. 단 오른쪽 또는 아래 방향으로만 이동이 가능하다.
- 방문한 칸에 있는 정수들의 합이 최소화되도록 프로그래밍해라.
행렬 - Key Obervation
- (i, j)에 도달하기 위해서는 (i, j - 1)이나 (i - 1, j)를 거쳐야 한다.
- 또한 (i, j - 1) 혹은 (i - 1, j)가지는 최선의 방법으로 이동해야 한다.
행렬 - 순환식
- L[i, j] : (1, 1)부터 (i, j)까지 도달하는 최소 경로의 길이 합
$m_{ij}$는 (i, j)위치에 저장되어 있는 숫자를 의미한다.
i와 j가 각각 1일때는 가장 구석에 위치해 있는 것으로 각각 오른쪽과 아래쪽으로만 움직일 수 없는 상태이다.
행렬 - Recursion
int mat (int i, int j) { // L(i, j)의 값을 계산해주는 메소드
if (i == 1 && j == 1)
return m[i][j];
else if (i == 1)
return mat(1, j - 1) + m[i][j];
else if (j == 1)
return mat(i - 1, 1) + m[i][j];
else
return Math.min (mat(i - 1, j), mat(i, j - 1)) + m[i][j];
}
-> Recursion방식은 중복이 많이 발생하여 비효율적이다.
행렬 개선 - Memorization
int mat (int i, int k) {
if (L[i][j] != -1)
return L[i][j];
if (i == 1 && j == 1)
L[i][j] = m[i][j];
else if (i == 1)
L[i][j] = mat(1, j - 1) + m[i][j];
else if (j == 1)
L[i][j] = mat(i - 1, 1) + m[i][j];
else
L[i][j] = Math.min (mat(i - 1, j), mat(i, j - 1)) + m[i][j];
return L[i][j];
}
행렬 개선 - Bottom Up
- 기본적으로 방향이 정해져 있는 것이 아니라 순환식의 왼쪽 값보다 오른쪽의 값이 항상 먼저 계산되는 것이 Bottom-Up방식이 된다.
int mat() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == 1 && j == 1)
L[i][j] = m[i][j];
else if (i == 1)
L[i][j] = L[1, j - 1] + m[i][j];
else if (j == 1)
L[i][j] = L[i - 1, 1] + m[i][j];
else
L[i][j] = Math.min (L[i - 1, j], L[i, j - 1]) + m[i][j];
}
}
return L[i][j];
}
- 조건식의 아래 3가지 경우를 하나로 합칠 경우 i나 j가 1일 때 제일 아래 식의 인덱스가 0이 되어 오류가 발생한다.
행렬 - Common Trick
- 조건식을 복잡하게 여러개로 나누지 않기 위해 간단한 트릭을 사용한다.
- 제일 왼쪽과 위쪽 배열을 전부 무한대로 채워놓으면 조건식을 복잡하게 나눌 필요가 없게 된다.
int mat() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == 1 && j == 1)
L[i][j] = m[1][1];
else
L[i][j] = m[i][j] + Math.min(L[i - 1][j], L[i][j - 1]);
}
}
return L[n][n];
}
-> 위 코드의 시간 복잡도는 $O(n^2)$이 된다.
행렬의 경로 구하기
- 경로를 구하기 위해서는 추가적인 배열을 만들어 이전에 선택했던 행렬의 방향값을 저장해준다.
int mat() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == 1 && j == 1) {
L[i][j] = m[1][1];
P[i][j] = '-';
}
else {
if (L[i - 1][j] < L[i][j - 1]) {
L[i][j] = m[i][j] + L[i - 1][j];
P[i][j] = '←';
} else {
L[i][j] = m[i][j] + L[i][j - 1];
P[i][j] = '↑';
}
}
}
}
return L[n][n];
}
//(arraylength, arraylength)좌표부터 시작하여 (0, 0)까지 되짚어가며 경로를 출력
void printPath() {
int i = n, j = n;
while (P[i][j] != '-') {
System.out.print(i + " " + j);
if (P[i][j] == '←')
j--;
else
i--;
}
System.out.print(i + " " + j);
}
//(0, 0)부터 거슬러 올라가는 방법
void printPathRecursive(int i, int j) {
if (P[i][[j] == '-')
System.out.print(i + " " + j);
else {
if (P[i][j] -- '←')
printPathRecursive(i, j - 1);
else
printPathRecursive(i - 1, j);
System.out.print(i + " " + j);
}
}
'프로그래밍 > 알고리즘' 카테고리의 다른 글
동적 계획법(4) - 행렬 곱 & LCS (0) | 2019.07.01 |
---|---|
동적 계획법 (3) - 퀵정렬 & 최장 경로 (0) | 2019.06.28 |
동적 계획법 (1) - Fibonacci & Binomial (0) | 2019.06.27 |
압축 (4) (0) | 2019.06.24 |
압축 (3) (0) | 2019.06.24 |