티스토리 뷰
이 단원에서는 Longest Common SubSequence문제와 그 Dynamic Programming 해법에 대해 알아본다.
LCS문제를 소개하기 위해 Sequence라는 것을 먼저 정의하자 Sequence는 다음과 같이 주어진 순서있는 집합이다.
X=<A,B,C,B,D,A,B>
이 때 X의 subsequence는 다음과 같이 X에 속하는 멤버들을 골라서 만드는 sequence를 얘기한다.
<B,C,D,B>, <A,B,A,B>
이 때 LCS문제는 다음과 같이 주어진다.
Q:X=<x1,...,xm>, Y=<y1,...,yn>의 common subsequence 중 그 길이가 가장 긴 Subsequence는?
input: X,Y
output: LCS Z
이 문제는 DP로 풀리는데, 다음과 같은 Thm을 생각해볼 수 있기 때문이다.
Thm15.1
X와 Y에서 LCS Z가 주어졌을때
(1) xm=yn인 경우: zk=xm=yn
(2) xm!=yn인 경우 & zk!=xm: zk=LCS of Xm-1& Yn
(3) xm!=yn인 경우 & zk!=yn: zk=LCS of Xm& Yn-1
따라서 recursive solution을 다음과 같이 구성할 수 있다.
C[i,j]를 Xi와Yj의 LCS 길이를 담은 해라고 했을 때,
C[i,j]= 0 when i=0 or j=0
c[i-,j-1]+1 when xi=yj
max(C[i,j-1],C[i-1,j]) when xi!=yj
그리고 xi=yj일 때 LCS멤버가 추가된 것이므로 LCS멤버구성을 위해 b[i,j]를 이용해 LCS를 찍어준다.
밑은 이것을 Bottom-up방식으로 구현한 Function과 Testcode를 포함한 코드이다.
#include<iostream>
using namespace std;
const int UPPERLEFT=100;
const int UPPER = 1000;
const int LEFT = 10000;
int** LCS(char* X, int x_size, char* Y, int y_size,int **b) {
int** c = new int*[x_size + 1];
for (int i = 0; i <= x_size; i++)
c[i] = new int[y_size + 1];
//c->LCS의 length저장
//b->화살표의 방향 저장
//c[i][j]: Xi와 Yj의 LCS 길이 저장
for (int i = 0; i <= x_size; i++)
c[i][0] = 0;
for (int j = 0; j <= y_size; j++)
c[0][j] = 0;
//최종 원하는 거: c[x_size][y_size]
for (int i = 1; i <= x_size; i++) {
for (int j = 1; j <= y_size; j++) {
if (X[i-1] == Y[j-1]) { //X의 i번째문자는 X[i-1]에 있기 때문에
c[i][j] = c[i - 1][j - 1] + 1;
b[i][j] = UPPERLEFT;
}
else if (c[i][j - 1] > c[i - 1][j]) {
c[i][j] = c[i][j - 1];
b[i][j] = LEFT;
}
else{
c[i][j] = c[i - 1][j];
b[i][j] = UPPER;
}
}
}
return c;
}
void printLCS(char* X, int i, char* Y, int j, int** b) {
if (i == 0 || j == 0) return; //basis case
if (b[i][j] == UPPERLEFT) { //xi==yj인 경우, LCS에 포함되는 경우임,
//순서가 recursive에서 거꾸로 출력해야 하니까 함수call-print가 되어야함.
printLCS(X, i - 1, Y, j - 1, b);
cout << X[i];
}
else if (b[i][j] == UPPER) { //i가 줄어든 경우, 즉 subLCS가 X쪽에 있었던 경우
printLCS(X, i - 1, Y, j, b);
}
else { //j가 줄어든 경우, 즉 subLCS가 Y쪽에 있었던 경우
printLCS(X, i, Y, j - 1, b);
}
}
int main() {
char X[] = "ABCBDAB";
char Y[] = "BDCABA";
int x_size = 7;
int y_size = 6;
int** b = new int*[x_size + 1];
for (int i = 0; i <= x_size; i++)
b[i] = new int[y_size + 1];
int** c = LCS(X, x_size, Y, y_size, b);
cout << "LCS length:" << c[x_size][y_size] << endl;
cout << "LCS of " << X << " and " << Y << " is ";
printLCS(X, x_size, Y, y_size, b);
return 0;
}
b를 이용해 LCS멤버를 print해주는 printLCS함수도 추가하였는데, 이것은 사실 C배열만 갖고 있어도 다시 찍어줄 수 있다. 우리는 생각해보면, BottomUp방식으로 했기 때문에, 함수 자체에서 LCS를 print해줄 수는 없지만 LCS를 Print하는데는 다음과 같은 원칙을 생각해보면 된다. 위의 재귀해를 다시 가져와보자.
C[i,j]= 0 when i=0 or j=0
c[i-,j-1]+1 when xi=yj
max(C[i,j-1],C[i-1,j]) when xi!=yjssssssss
그러면 우리가 LCS에 멤버를 추가하는건 두번째 경우로 xi=yj일 때 뿐이다.
만약, 이것을 top-down방식으로 풀고 두번째 경우를 볼 때마다 특정 stack에 이 멤버를 추가한다면 이 stack을 pop하는 것만으로도 LCS를 구성할 수 있게 되리란 것을 예상해볼 수 있다. 그런 의미에서 b배열은 bottom-up방식으로 문제를 해결하면서 top-down방식으로 문제를 풀 때에 어떻게 해가 재귀적으로 풀리게 되리란 것을 tracing해놓은 변수가 될 것이다.
그리고 책에서 Optimal Substructure성질에 대한 언급이 있는데, 핵심은 문제를 substruct화 할 수 있다는 것이 아니다. 핵심은 sub문제간 독립성이다. Longest simple path가 DP로 풀 수 없는 대표적인 문제로 꼽힌다.
'알고리즘의 기초' 카테고리의 다른 글
4-(1) Dynamic Programming 2)Matrix Chain Multiplication (0) | 2020.02.03 |
---|---|
4-(1) Dynamic Programming 1)Rod Cutting (0) | 2020.02.02 |
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
- 매트랩 함수
- gradient descent
- 컴퓨터과학
- RGB이미지
- Logistic Regression
- 머신 러닝
- 영상구조
- 신호 및 시스템
- 컴퓨터 과학
- Neural Network
- 이미지
- 사진구조
- 인덱스 이미지
- ML
- NLP
- 신경망
- rnn
- 이미지처리
- 연속 신호
- 딥러닝
- 영상처리
- CS
- 이산 신호
- 밑바닥부터 시작하는 딥러닝
- 자연어 처리
- 매트랩
- Andrew ng
- 순환 신경망
- 머신러닝
- CNN
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |