예시) 팩토리얼
5!
=5x4!
=5x4x3!
=5x4x3x2!
=5x4x3x2x1
function fac(n){
if(n === 1) return 1;
return n * fac(n-1);
}
fac(4) // 결과 24
1. 문제를 잘게 쪼개고 경우의 수를 생각해본다.
2. 탈출 조건을 정한다.
배열의 합 구하기
let arr = [1,2,3,4,5]
console.log(arrSum(arr)); // 결과 : 15
배열의 요소의 합을 구하는 함수는 for문으로 작성가능하지만 for문을 사용하지 않고 재귀 함수를 통해 작성 가능하다.
먼저, 배열의 첫번째 요소와 그 뒤의 요소들로 나눌 수 있다.(크기를 나눈다.)
1 + [2,3,4,5]
다시 뒤의 배열을 첫번째 요소와 나머지 배열로 나눌 수 있다.
2 + [3,4,5]
3 + [4,5]
4 + [5]
5 + []
위의 예시에서 탈출 조건은 마지막의 빈 배열일때 값을 0으로 해주거나 아니면 배열의 길이가 1일때 요소를 return한다.
if(arr.length === 0){
return 0;
}
if(arr.length === 1){
return arr[0];
return arr[0] + arrSum(arr.slice(1)) // 크기를 나눈 코드
해결
코딩으로 옮겨보자
function pow(x,n){
if(n === 1) {
return x
}
return x * pow(x,n-1);
}
재귀함수 사용 시 메모리의 한계
함수호출 횟수가 많아 끝나지 않는 연산
function fibonacci(num){
if(num <=1){
return num;
}
return fibonacci(num - 1) + fibonacci(num - 2);
반복문으로 해결한다.
function fibonacciLoop(num){
let first = 0, second = 1
let current = 0;
if(num <= 1){
return num
}
for(let i = 2; i <= num; i++){
current = first+second
first = second
second = current
}
return current
}
Tail Call이란
function a() {
var v = 0;
return b(v--);
}
function b(n) {
return n + 1;
}
a(); //결과 : 1, 우리가 생각하는 Tail Call로 코딩해보았지만 값은 0이 아닌 1이다. 그 이유는, --(decrement)에 있다. 3번째 줄에서 b의 인자값을 --v로 해야한다. 계산된 값을 넘겨줘야된다.
//그래서 위의 코드는 Tail Call이 아니다. Tail Call을 하기 위해서 b(--v)로 해야한다.
Tail Call Optimization이란
Tail Recursion으로 호출 함수 횟수 줄이기
function fibonacciTailRecursion(num, first, second){
let current;
if(num <=1){
return num * second // second값을 곱해주어 계속 앞의 두수를 더한 값을 return한다.
}
//
current = first+second
first = second
second = current
return fibonacciTailRecursion(num - 1, first, second);
}
//fibonacciTailRecursion(3,0,1)의 값은?
fibonacciTailRecursion(3,0,1)
=fibonacciTailRecursion(2,1,1)
=fibonacciTailRecursion(1,1,2) // num = 1*2
//fibonacciTailRecursion(4,0,1)의 값은?
fibonacciTailRecursion(4,0,1)
=fibonacciTailRecursion(3,1,1)
=fibonacciTailRecursion(2,1,2)
=fibonacciTailRecursion(1,2,3) // num = 1*3
//fibonacciTailRecursion(100,0,1)의 값은?
fibonacciTailRecursion(100,0,1)
=fibonacciTailRecursion(99,1,1)
=fibonacciTailRecursion(98,1,2)
=fibonacciTailRecursion(97,2,3)
....
=fibonacciTailRecursion(1,엄청큰수1,엄청큰수2) // num = 1*엄청큰수2, 함수 호출 횟수는 100번이다.
재귀 함수로 num까지의 합을 구해보자
function sum(num){
let acc = 0
if(num === 1){
return num
}
return num + sum(num - 1);
}
sum(100000) // Uncaught RangeError: Maximum call stack size exceeded
Tail Recursion 방식으로 num까지의 합을 구해보자
function sumTailRecursion(num,acc){
if(num ===0){
return acc;
}
acc += num;
return sumTailRecursion(--num,acc)
}
sumTailRecursion(100000,0) // Uncaught RangeError: Maximum call stack size exceeded
반복문으로 Stack 메모리 문제를 해결
function sumLoop(num){
let acc = 0;
while(n){
acc += n--;
}
return acc;
}
참고한 사이트
사용하는 경우
1. 주어진 문제가 (구조는 비슷하고) 더 작은 문제로 나뉘어 질 수 있는 경우
2. 중첩된 루프가 많거나 중첩의 정도(number of loops)를 미리 알 수 없는 경우
재귀함수의 활용(트리 구조)
1. 트리 구조에 재귀 함수를 활용
2. JSON 구조에 재귀 함수를 활용
3. DOM 구조에 재귀 함수를 활용
주의 사항
힌트 얻기
문제 분해
1. hanoi(N,start,to,via): start에서 to로 via를 거쳐 총 N개의 원반을 운반할 때 각 이동 과정을 출력자
1-1. 각 원반의 출발지와 목적지가 다르다.
1-2. 중간에 경유지가 있다.
2. hanoi(3,'A','C','B')가 문제일 때,
코딩에서는 console.log로 출력
function move(N, start, to){
console.log(`${N}번 원반을 ${start}에서 ${to}로 이동`)
}
function hanoi(N, start, to, via){
if(N===1){
move(1,start,to)
}else{
hanoi(N-1,start,via,to)
move(N,start,to)
hanoi(N-1,via,to,start)
}
}
function hanoiCount(N){
console.log(Math.pow(2,N) - 1)
}
hanoi(3,'A','C','B')
//else
hanoi(2,'A','B','C') //hanoi함수의 다섯번째 줄
//else
hanoi(1,'A','C','B') // 1번 원반을 A에서 C로 이동
move(2,'A','B')// 2번 원반을 A에서 B로 이동
hanoi(1,'C','B','A') // 1번 원반을 C에서 B로 이동
move(3,'A','C') // 3번 원반을 A에서 C로 이동
hanoi(2,'B','C','A')
//else
hanoi(1,'B','A','C') // 1번 원반을 B에서 A로 이동
move(2,'B','C') // 2번 원반을 B에서 C로 이동
hanoi(1,'A','C','B') //1번 원반을 A에서 C로 이동
출처 : https://shoark7.github.io/programming/algorithm/tower-of-hanoi