재귀가 뭐냐고 물었을 때 어그부츠 장사하다가 설명하는 것을 포기했었다. 이에 그치지 않고 재귀함수에 대해 쉽게 설명할 수 있게 정리해 본다.
일단 한국어의 재귀
라는 것에 대해서 알면 더 쉬워진다. 생각보다 네이밍을 직관적으로 짓기 때문에 어원을 찾고 머리에서 그림만 그릴 줄 안다면 절반은 이해했다고 본다. '본디의 곳으로 다시 돌아오는 것.'을 재귀라고 한다. 함수는 수직으로 진행이 된다. 했던 것을 다시 돌아 또 하지는 않는다. 재귀함수라는 것은 했던 것을 또 하려고 만들어진 이름처럼 보인다. 재귀함수에 대해서 알아보자.
어떠한 함수에서 자신을 다시 한 번 호출하여 작업을 수행하는 방식. 반복문을 사용하는 코드는 재귀로 표현이 가능하고, 반대로 재귀로 표현할 수 있는 코드는 반복문 사용이 가능함. 재귀함수를 사용한 시점부터 그 사용이 끝날 때까지 다음 명령은 수행되지 않으며, 반복적으로 임하는 것이기 때문에 항상 재귀함수를 탈출할 수 있는 탈출구를 만들어 두어야 한다.
회를 거칠 때마다 점점 작아진다는 것은 재귀함수를 돌릴 수 있는 횟수에 제한을 둔다는 것이다. 제한을 두지 않으면 마치 패턴처럼, 반복되는 미로처럼 계속 돌고 돌기 때문에 다음 명령을 수행할 수도, 그렇다고 함수가 끝나지도 않기 때문에 조금씩 작아지게 만들어서 끝낼 수 있게 만들어야 된다.
마지막엔 사탕이나 초콜릿 같은 어떠한 보상이 있다는 것은, 이 재귀함수(마트료시카)가 끝이 나면 어떠한 결과를 도출한다는 점과 같다. 뇌 빠지게 반복적으로 행동을 행했는데 보상이 없다? 거의 대모 일어나는 거거든요. 공장 알바 할 때 일급 안 주는 거랑 같은 이치거든요. (공장 알바 경험한 사람이라 매우 분노 중) 재귀함수는 '함수'이기 때문에 return 값이 필수적으로 있고, 그 리턴 값을 가지고 다시, 다시, 다시 자신을 통과하여 마지막으로 나온 리턴 값이 사탕이나 초콜릿처럼 달고 값지기 때문에 비슷하다고 할 수 있다. 있기 때문에 초콜릿 같은 보상과도 같다고 볼 수 있다.
자바스크립트로 표현함
가장 대표적인 팩토리얼 예시
function fac(n) {
if(n===1) {return 1}
return n * fac(n-1)}
fac(5) //120
팩토리얼이란? 1부터 n까지 차례대로 곱한 값 (n!라고도 씀.)
5!를 구한다고 했을 때, 5 x 4 x 3 x 2 x 1로 표현할 수 있는데, 수가 하나씩 줄어들고 작아지는 게 마트료시카와 꽤 닮았다. 게다가 마지막엔 120이라는 값을 받기도 하니 사탕도 두둑이 챙길 수 있다.
위의 함수를 풀이했을 때,
fac(5)는
5 x fac(5-1) x fac(4-1) x fac(3-1) fac(2-1)
로 보여지게 되는데, 5 x fac(5-1)을 끝내고 20 x fac(4-1)로도 할 수 있지 않느냐? 라고 한다면 비슷해 보이지만 다르다고 말할 수 있다. 재귀함수가 호출이 되면, 재귀가 끝나기 전까지는 다음 명령을 호출할 수 없다는 특성 때문이다. 5 뒤로 나오는 fac 함수는 계속 쌓인다. 스택이 쌓인다고 볼 수 있다. (n이 1이 되면 멈추라고 하는 if문이 있기 때문에 n은 1에서 멈추고, 재귀함수에서 빠져나올 수 있다.)
fac(2-1) |
---|
fac(3-1) |
fac(4-1) |
fac(5-1) |
5 * |
이런 식으로 차근차근 쌓이게 된다. 그리고, 위에서부터 다시 차근차근 실행하며 곱해 주는 것이다. 결론적으로는 5 x 4 x 3 x 2 x 1를 하게 되는 것.
어차피 했던 것을 또 해 주는 것이기 때문에 앞서 말했던 것처럼 반복문으로도 풀 수 있다.
function fac(n) {
let j = 1;
for(let i = 1; i<=n; i++) {
j = j * i;
return j
}
fac(5) //120
i의 값이 n과 같아질 때까지 반복문을 실행하는 것으로 제한을 뒀고, i는 1부터 1씩 증감하며, 그 값을 j라는 변수에 담았다. (j는 1이어야 한다. 그냥 변수 선언만 해 주면 숫자라는 것을 모르고, 0이라고 하면 곱셈이기 때문에 계속 0이 되기 때문.)
반복문에서는 1 x 1, 1 x 2, 2 x 3, 6 x 4, 24 x 5의 순서로 차근차근 계산해나간다.
1 x 2 x 3 x 4 x 5를 하게 되는 것. (물론 5에서부터 하나씩 줄어들게 할 수도 있다.)
앞서 말했던 것처럼 어떠한 것을 반복하려고 할 때는 재귀와 반복문, 둘 다 사용이 가능하다. 그렇다면,
재귀함수의 장점: 직관적이다. 반복문보다 코드가 깔끔하다. 변수 사용이 줄어든다.
반복문의 장점: 재귀함수보다 쉽다.(ㅎㅎ) 원하는 것만 알차게 반복할 수 있다. 재귀보다 빠르다.
재귀함수의 단점: 메모리가 많이 든다. 함수 자체를 다시 돌리는 것이기 때문에, 팩토리얼처럼 단순한 계산이 아닌 피보나치나 조금 더 복잡한 계산을 하게 되어 함수가 커지게 되면 계산할 것들이 많아지게 되고, 느려지고, 메모리 소비가 크다.
반복문의 단점: 재귀보다 직관적이지 않다. 쓸데없는 변수를 사용하게 된다. 코드 길이가 길어진다.
그러므로, 재귀와 반복문의 장단점을 파악하여 적재적소에 쓸 수 있어야 한다. 재귀로 시작했지만 끝은 재귀와 반복문인 이유가 여기에 있다. 재귀가 더 예쁘고 좋아 보이고, 반복문을 대체할 수 있고, 코드를 봤을 때 굉장히 쉬워 보이는데 재귀만 쓰는 게 낫지 않을까요?(내가 그런 생각을 했음.)라고 생각할 수 있는데, 어떠한 게 자신에게 맞고 특출나게 좋아서 그것만 쓰는 것은 좋지 않다. 각각의 장단점이 있기 때문에 그들이 최상의 컨디션을 지킬 수 있는 자리에 알맞게 놓아 주는 게 효율적인 코드를 작성하는 데에 있어서 좋지 않을까 하는 생각을 하고 있다. ㅎㅎ
개인적으로 재귀는 어떠한 값을 바꾼 상태로 함수를 실행하고 싶을 때 쓰는 게 가장 예뻐 보인다.
functin foo(a, b) {
if(a !== 5) {return foo(5, b)}
return a + b}
foo(1,2) //7
//진짜 쓸데없는 함수이고 사용해서도 안 되지만.. 이런 식으로 어떠한 것을 미리 바꿔 놓고 함수를 실행하게 하는 게 제일 좋다.
틀린 부분이 있거나 첨언하고 싶은 내용이 있다면 댓글로 말씀해 주세요. 공부하는 데에 도움이 됩니다.
참고 문서
https://marobiana.tistory.com/79
https://gomguard.tistory.com/111