코딩테스트/C++

[C++] 재귀 "우박수 길이(3n+1)"

SK_MOUSE 2020. 9. 16. 16:32
반응형

#include <iostream>

using namespace std;

long long int memo[10000001] = { 0 };


long long int recur(long long int n) {
   if (n > 10000000) {//메모리보다 크면 배열사용안함.
      if (n % 2 == 0) return recur(n / 2) + 1;
      else return recur(n * 3 + 1) + 1;
   }
   
   //여기부터 시작.
   if (memo[n] != 0) return memo[n];//메모이제이션
   else {
      if (n == 1) {
         return 1;//실행된 횟수 반환.
      }
      else if (n % 2) {
         return memo[n] = recur(3 * n + 1) + 1;
      }
      else {
         return memo[n] = recur(n / 2) + 1;
      }
   }
}

int main() {
   int n = 0, m=0;
   
   int many=0, manyi=0;//우박수 많은 i와 최대우박수
   cin >> n >> m;
   
   for (int i = n; i <= m; i++) {
      long long int check = recur(i);

      if (check > many) { //recur(i) > recur(i-1) 비교
         manyi = i;
         many = check;
      }
   }
   cout << manyi << " " << many;
}

 

먼저 main문을 먼저 살펴보자.

 

변수

many : 최대 우박수(recur함수의 계산값)

manyi : 최대 우박수일때, 그때의 i값

 

long long int 값을 통해 recur함수의 반환값을 받는 방식이다.

 

if문은 구한 check값 중 최대값을 산출하기 위해 비교하는 조건문이다.

 

요약 :

1. 매번 반복문을 i값을 차례대로 계산해보면서 "최대우박수"를 리턴받는다.

2.그 우박수가 최대면 i값과 그 값을 저장한다


recur 함수를 살펴보자.

 

처음에 적혀있는 조건문은 해당 지문에서 10,000,000보다 큰 값을 받았을때 처리하는 부분이므로 무시해도 된다.

 

두번째 if((memo[n] != 0) 부분은 메모이제이션에 값이 들어있다면 그 값을 바로 리턴한다.

이 부분은 메모이제이션에 관한 부분이므로, 일단 이렇게 간단하게 설명하겠다.

상황의 예시를 하나 제시하겠다.

 


메모이제이션

상황 : recur(4)값을 구해야되는데 아래의 재귀에 따라서 recur(4)보다 작은 값들을 계속해서 호출해야한다.

 

이 상황에 미리 그에 대한 값을 recur(3) recur(2) recur(1) 등을 계산할때 배열memo에 저장해놓으면, 굳이 처음부터 계산할 필요가 없는 것이다.

 

즉, 수학적으로 실행속도 측면으로 바라보자면, 재귀는 똑같은 구문을 조건에 따라서 

n!의 시간복잡도를 n*(n-1)*(n-2)*(n-3) ... 이러던 시간복잡도에서,

예를들어 memo[1] memo[3]의 값이 저장되어있어서 바로 불러왔다면 n*(1)*(n-2)*(1)

이런식으로 한번에 값을 불러오기때문에 시간단축시켜주어서 코드의 효율성을 높여준다.

 


재귀 작동원리

자, 이제 메모이제이션에 대해서 설명을 마쳤으니, recur 재귀함수의 작동원리에 대해 알아보겠다.

리턴할때 +1씩 해주는 것은 각 시행에 따라서 실행된 횟수를 더해주는것이다.

 //여기부터 시작.
   if (memo[n] != 0) return memo[n];//메모이제이션
   else {
      if (n == 1) {
         return 1;//실행된 횟수 반환.
      }
      else if (n % 2) {
         return memo[n] = recur(3 * n + 1) + 1;
      }
      else {
         return memo[n] = recur(n / 2) + 1;
      }
   }

위 부분에서 if부분은 앞서 말한 메모이제이션으로 시간효율을 위해서 사용하는 부분이다.

 

else 부분부터 보면, 우리가 예를들어 recur(4)를 호출하게 된다면

 

#1

<recur(4)>

 

if(n은 1이아님)

else if(4%2 = 0 =false이므로 아님)

else return memo[4] = recur(4/2) +1 이므로 recur(2)를 호출을 먼저 하여 그 값에 1을 더해준다.

<recur(2)>

if(n은 1이아님)

else if(2%2) = 0 = false이므로 아님)

else return memo[2] = recur(1) +1 이므로 recur(1)를 호출을 먼저 하여 그 값에 1을 더해준다.

<recur(1)>

if(n=1) return 1;

 

재귀의 과정

따라서 return memo[2] = 1 + 1 이고 그 값을 리턴한다.

return memo[4] = 2 + 1 이므로 return memo[4] =3을 한다.

 

메모이제이션에는 memo[2]=2  memo[4]=3 이 저장되었다.

 

 

 

다음은 i값이 5인 상황을 살펴보겠다

#2

<recur(5)>

else if(5%2 = 1 = true)

   return memo[5] = recur(16) + 1

<recur(16)>

else

   return memo[16] = recur(8) + 1

<recur(8)>

else

  return memo[8] = recur(4) +1

이때 메모이제이션이 활용된다.

<recur(4)> -> <recur(2)> -> <recur(1)>의 과정을 거치지 않고,

바로 recur(4) = memo[4] = 3 을 가져온다.

 

 

재귀의 과정

return memo[8] = 3 +1 = 4

 

return memo[16] = 4 + 1 = 5

 

 

이렇게 메모이제이션에는

memo[2]=2

memo[4]=3

memo[8]=4

memo[16]=5

의 값이 저장되었다.

 

이와 같은 방식으로 작동한다.

 

 

반응형

'코딩테스트 > C++' 카테고리의 다른 글

C++ Codeup 기초100제-1  (0) 2020.08.04