[2024 하계모각코] 2주차 결과

2024. 7. 8. 15:52·스터디/2024하계모각코

Dynamic Programming-1

*Richard Bellman이 만들었다.

 

피보나치 수열

1 1 2 3 5 8 13

f(n)=f(n-2)+f(n-2) n>2

f(1)=f(2)=1

 

int fib(int n)

{ if(n==1 || n==2) return 1;

else return fib(n-2)+fib(n-2);}

 

->순환으로 하면 매우 비효율적


Memoization

Memoization으로 풀면, 1차원 배열인 cashe에 저장하기 때문에, 동일한 피보나치 수열을 중복 계산 안 해도 된다.


 

순환식을 bottom-up방식으로 풀면,  중복 계산을 피할 수 있다. -> Dynamic Programming


 

 

base case가 있을 때, base case가 아닌 경우에 순환을 할 때 마지막엔 base case를 꼭 거치도록 해야 한다.

이때, n>=k일 때 여야 한다. 이때, 역시 많은 계산이 중복되므로 Memoization을 해야 한다.


 

 

2차원 배열을 만들고, n>=k일때, 대각선 위는 무시, 대각선 아래만 의미있는 값으로 본다.

중간 계산 결과를 cash에 저장한다.


 

 

bottom-up방식: binom[i][j] = binom[i-1][j-1] + binom[i-1][j] 순환식의 오른쪽 식이 왼쪽 식보다 먼저 계산된다.

행우선 순서로 계산한다.


 

Memoization vs Dynamic Programming

  • 순환식의 값을 계산하는 기법들이다.
  • 둘 다 동적계획법의 일종으로 보기도 한다.
  • Memoization은 top-down 방식임, 실제로 필요한 subproblem만을 푼다.
  • 동적계획법은 bottom-up 방식이며, recursion에 수반되는 overhead가 없다.

Dynamic Programming-2

1. 행렬 경로 문제

2차원 배열에 정수 값들이 저장되어 있을 때, 좌상단부터 우하단까지 이동하며, 오른쪽이나 아래쪽 방향으로만 이동할 수 있다. 왼쪽이나 위쪽 방향으로는 이동할 수 없다. 방문한 칸에 있는 정수들의 합이 최소가 되도록 해야 한다.

 

먼저 순환식을 세우고 그다음 계산을 하면 된다.


 

  • (i,j)에 도달하기 위해서는 (i,j-1)혹은 (i-1,j)를 거쳐야 한다. 오른쪽이나 아래쪽 방향으로만 이동할 수 있기때문에 거꾸로 생각했을 때 이렇게 되는 것이다.
  • 또한, (i,j-1)혹은 (i-1,j)까지는 최선의 방법으로 이동해야 한다.

L[i,j]가 (1,1)에서 (i,j)까지 이르는 최소합을 의미한다.

 

이때, 만약 i=1,j=1이면(2차원 배열에서 첫번째 값이면), 그 값이 최소합이 된다.

만약 j=1이면(첫번째 열의 가장 하단의 값까지 이동), i-1(최하단 행에서 하나 윗 행)까지 이동했을때의 L값에 mij를 합하면 그 값이 최소합이 된다.

마찬가지로 i=1일 때는 j=1일 때와 같이 외길로만 이동한다.

마지막 case에서는 L[i-1,j]+mij와 L[i,j-1]+mij중 최솟값이 답이 된다.


int mat(int i, int 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];}

앞의 식을 코드로 짠 것이다. 하지만 이럴 경우 중복이 있을 수 있다.


mat(3,3)같은 경우 중복이 처음으로 발생한다. 이처럼 중복이 발생하므로 Memoization방법을 써주도록 하자.


int mat(int i, int j)
{	
	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];
 }

Memoization방법을 써서 계산의 중복을 피했다. 

2차원 배열 L을 만들어 미리 계산된 값(-1)을 저장해준다. 

미리 저장된 -1이 아니면 L[i][j]를 리턴한다.

 

전에는 값을 저장하지 않고 바로 리턴했는데, 이 방법에서는 값을 저장한 다음 마지막에 리턴하여 중복을 피한다.


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[1][1];
   		 	else if(i==1)
    			L[i][j] = L(i,j-1)+m[i][j];
   		 	else if(j==1)
    			L[i][j] = L(i-1,j)+m[i][j];
    		else
    			L[i][j] = Math.min(L(i-1,j), L(i,j-1)) + m[i][j];
            }
    }
    return L[n][n];
 }

위 함수의 시간복잡도는 O(n의 제곱)이다.


Common Trick

/*initialise L with L[0][j]=L[i][0]=무한대 for all i and j*/

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의 제곱)이다.


경로 구하기

28을 계산할 때 위의 값인 25 또는 왼쪽의 값인 31 중 작은 값에 원래 있던 값인 3을 더해서 28이 된 것이다.

이러한 과정을 화살표로 표현한 것이 P행렬이다.


/*initialise L with L[0][j]=L[i][0]=무한대 for all i and j*/

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];
}

 위 함수의 시간복잡도는 O(n의 제곱)이다.


void printPath()
{
	int i=n, j=n;
    while(P[i][j] != '-'){
    	print(i+" "+j);
        if(P[i][j]=='<-')
        	j=j-1;
        else
        	i=i-1;
     }
     print(i+" "+j);
 }

우측 최하단(목적지)에서 좌측 최상단까지 경로가 거꾸로 출력된다.

그렇다면 반대로 경로가 처음 있었던 곳에서 목적지까지일 경우 함수는 어떻게 작성할까?


void printPathRecursive(int i, int j)
{
	if(P[i][j] == '-')
    	print(i+" "+j);
    else{
        if(P[i][j]=='<-')
        	printPathRecursive(i,j-1);
        else
        	printPathRecursive(i-1,j);
        print(i+" "+j);
     }
 }

(1,1)에서 (i,j)까지의 최소합경로인 출발점에서부터 목적지까지의 경로를 출력한다.

'스터디 > 2024하계모각코' 카테고리의 다른 글

[2024 하계모각코] 3주차 결과  (0) 2024.07.15
[2024 하계모각코] 3주차 목표  (0) 2024.07.15
[2024 하계모각코] 2주차 목표  (0) 2024.07.08
[2024 하계모각코] 1주차 결과  (0) 2024.07.01
[2024 하계모각코] 1주차 목표  (0) 2024.07.01
'스터디/2024하계모각코' 카테고리의 다른 글
  • [2024 하계모각코] 3주차 결과
  • [2024 하계모각코] 3주차 목표
  • [2024 하계모각코] 2주차 목표
  • [2024 하계모각코] 1주차 결과
soyun5
soyun5
개발블로그
  • soyun5
    soyun5
    soyun5
  • 전체
    오늘
    어제
    • 분류 전체보기 (27)
      • 운영체제및실습 (0)
      • 프로그래밍언어개론 (0)
      • 데이터베이스 (0)
      • 클라우드 (0)
        • 2025 AWS Cloud 초급특강 (0)
      • 스터디 (23)
        • 2024하계모각코 (10)
        • 2024동계모각코 (13)
      • 웹개발 (0)
        • HTML, CSS, 자바스크립트 (0)
      • PS (0)
        • 백준 (0)
        • 프로그래머스 (0)
      • 앱개발 (0)
        • flutter (0)
      • C (0)
      • java (0)
        • 자료구조 (0)
        • 알고리즘 (0)
      • 깃허브 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Directory
    운영체제
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
soyun5
[2024 하계모각코] 2주차 결과
상단으로

티스토리툴바