학창시절.. 수학시간에 프랙탈이라는 구조에 대해서 한번쯤은 들어봤을 것이다. 동일한 모양이 계속해서 반복되는 그러한 구조…!! 사진 속 시에르핀스키 피라밋처럼! 프로그래밍 세계에서도 동일한 함수가 계속 반복되는 재귀(Recursion)라는 개념이 있다.
왜 재귀를 공부해야 할까?
- 간결하고 직관적인 코드를 제공한다!
- 설계와 개발, 디버깅 과정모두 재귀적인 사고를 요구한다!
재귀란?
- 자기 자신을 호출하는 행위
- 주어진 문제를 자기 반복적인 문제들로 잘게 분해한 후, 이들을 다시 조합해 원래 문제의 정답을 찾는 것을 말한다~
재귀와 콜스택
- 스택(Stack)이란?
-함수를 호출한 후 원래 자리로 돌아오려면, ‘원래 자리’를 어딘가에 저장해야하는데 그 어딘가를 가리켜 Stack이라고 한다.
- 호출스택(Call Stack)이란?
-프로그램상 어디에 있는지 기록하는 자료구조를 말한다
cf) MDN 호출스택 정의 : https://developer.mozilla.org/ko/docs/Glossary/Call_stack
-함수를 실행하면 stack위에 push를 하게 됩니다.(쌓이고~)
-return 시 stack의 맨 윗값(가장 최근 데이터)을 pop합니다.(가져오면 그 함수가 cal l stack에서 제거됩니다.)
==> 그래서 return 이란~!
Stack에 저장된 최근 주소값, 나를 실행시킨 것의 바로 다음 step의 주소로 돌아간다는 의미입니다.
- Stack Overflow
-값이 return 되기 전에 call stack이 쌓이면 호출스택의 최대 수용치를 넘게 되고… stack overflow 발생!!
-종료조건을 달아줘야 stack overflow를 막을 수 있다.
재귀함수 응용
<1>피보나치 수열
- 첫 달은 아기 토끼 한 쌍에서 시작합니다.
- 아기 토끼는 한 달이 지나면 어른토끼가 됩니다.
- 어른 토끼는 한 달에 한 쌍의 아기토끼를 낳습니다.
==> 0,1,1,2,3,5,8 … 위의 결과가 나오도록 함수를 만들어 보면.. 🙂
function fibo(n) {
if (n < 2)
return n;
return fibo(n-1) + fibo(n-2);
}
fibo(6) // 8
제가 이 코드를 칠 때 우리의 컴퓨터우리의 컴퓨터는…
call fibonacci(6)
call fibonacci(5)
call fibonacci(4)
call fibonacci(3)
call fibonacci(2)
call fibonacci(1)
return 1
call fibonacci(0)
return 0
return 1
call fibonacci(1)
return 1
return 2
call fibonacci(2)
call fibonacci(1)
return 1
call fibonacci(0)
return 0
return 1
return 3
call fibonacci(3)
call fibonacci(2)
call fibonacci(1)
return 1
call fibonacci(0)
return 0
return 1
call fibonacci(1)
return 1
return 2
return 5
call fibonacci(4)
call fibonacci(3)
call fibonacci(2)
call fibonacci(1)
return 1
call fibonacci(0)
return 0
return 1
call fibonacci(1)
return 1
return 2
call fibonacci(2)
call fibonacci(1)
return 1
call fibonacci(0)
return 0
return 1
return 3
return 8
위의 식으로 6번째 피보나치 수를 구하는데 무려 함수의 호출이 25번 일어납니다 ㅠㅠ.. 14번째 피보나치 수를 구할땐 호출수는 1019…
console.log(fibo(100)) 을 찍어보면 함수의 호출이 몇 번 일어날까요? (컴퓨터 살려…)
재귀함수의 문제점
- 함수 호출의 비용
- Stack의 깊이
그렇다면 해결책은 없을까요…?!
해결책 : 함수 호출하지말고 반복문 쓰기
-반복 단계별 계산 결과를 반복이 끝날 때까지 특정 변수에 저장하는 방식으로 풀어볼 수 있습니다~
function fibo(n) {
var cur, pre = 1, prepre = 0;
if (n < 2)
return n;
for ( var i = 2 ; i <= n ; i++ ) {
cur = pre + prepre;
prepre = pre;
pre = cur;
}
return cur;
}
또 다른 해결책으론 꼬리호출이라는 방식이 있는데.. 이건 제가 아직 제대로 이해하지 못해서 다음 기회에…
아직 이 방식을 적용하는 브라우저도 많지 않아서 천천히 알아도 될 것 같고요..?
재귀함수 활용한 알고리즘 문제들
전체코드를 다 보기 전에 타이틀만 보고 꼭 한번씩 생각해보세요~
최소공배수와 최대공약수 구하기
function solution(n, m) {
function u(n, m) { return m % n ? u(m % n, n) : n; }
const gcd = u(n, m);
return [gcd, n * m / gcd];
}
유클리드 호제법이라는 것도 한번 찾아보세요~ 아직도 너무 너무 헷갈리는 이 공식 ㅠㅠ
문자열 반복하기
function repeatString(string, num) {
if(num <= 0) {
return ''
}
else {
return string + repeatString(string,num-1)
}
}
주어진 두 수 사이의 숫자들 구하기
const range = (start = 0, end = 0) => {
let arr = [];
start > end && ([start, end] = [end, start]);
arr.push(start);
return start === end ? arr : [...arr, ...range(start + 1, end)]
}
console.log(range(2, 5)); // [2, 3, 4, 5]
N번째 짝수 구하기
function getNthEvenNum (n) {
if (n <= 1) {
return 0;
} else {
return getNthEvenNum(n-1) + 2;
}
};
getNthEvenNum(3) //4
//0,2,4,6,8,10,12...
특정숫자의 n제곱 구하기
아래의 코드는 2의 n제곱이지만 다른 숫자를 넣으면 그 수의 제곱을 구할수 있겠죠? :)
function getPowerOf2 (n) {
if(n) <=0) {
return 1
}
else {
return getPowerOf2(n-1)*2
}
}
getPowerOf2(4)//16
순차검색법
이건 아직도 헷갈려서 이번에 정리하는 김에 다시 보고 있어요~
function searchArraySequentially (array, i, j, x) {
if (i <= j) {
if (array[i] === x) { // 같으면 i return
return i;
} else { //같지 않으면 하나씩 늘려서 간격 줄여가기
return searchArraySequentially(array, i + 1, j, x);
}
} else { //끝까지 줄였는데도 안 나오면 i와j사이에 없는거니깐 -1 return
return -1;
}
}
var array = ['a', 'b', 'c', 'd', 'e'];
var result1 = searchArraySequentially(array, 0, 4, 'e');
var result2 = searchArraySequentially(array, 0, 3, 'e');
문자열 역순으로 정리하기
거꾸로 하는건 reverse지만.. 언어유희 좀 해봤어요 ㅎ 이 코드를 알고있어서 위코드 1주차 코드카타 문제를 재귀로도 접근해볼수 있었네요~
function rebirth(s) {
if(s === '') {
return ''
}
else {
return rebirth(s.substring(1))+s.charAt(1)
}
}
console.log(rebirth('hello'))
//ello + h
//llo + e + h
//lo + l + e + h
//o + l + l + e+ h
이렇게 재귀에 대해 간단히 알아보았는데요..!
개발자라면 피할 수 없는 부분이라고 생각합니다.. 탈출조건을 생각하고 로직을 짜는게 참 어려운 일이지만..
익숙해지면 잘 할수 있겠죠???!ㅋㅋ
Reference
- https://ryulib.tistory.com/318
- https://homoefficio.github.io/2015/07/27/%EC%9E%AC%EA%B7%80-%EB%B0%98%EB%B3%B5-Tail-Recursion/
원래 출처가 몇 군데 더 있었는데.. 예전에 에버노트에서 한번 날리는 바람에…ㅠㅠ
출처 중 두번째 블로그는 읽어보시면 꼭 도움 될겁니다!