JS 자료구조 & 알고리즘 - 재귀
재귀 함수란
자기 자신을 호출하는 함수를 말한다.
- 재귀 함수의 필수요건
- 종료 조건 (라인을 끝내는 지점)
- 다른 입력값
JS 에서 함수를 호출할때 발생하는 일
- JS는 call Stack이라는 구조 안에서 함수가 실행 된다.
call Stack은 이름 그대로 stack
자료구조를 사용하고 있다.
call Stack에는 우리가 호출한 함수가 늘 제일 위에 위치하게 된다.
callStack으로 호출된 함수는, return 키워드를 확인하거나, 더이상 실행할 코드가 없으면
컴파일러
가 callStack의 최상단에 있는 지금 실행중인 함수를 제거한다.
나중에 들어온함수가 제일 먼저 사라지게 되는 구조!
LIFO 라고 한다.
간단한 재귀함수
1
2
3
4
5
6
7
8
9
10
function countDown(num) {
if(num <= 0) {
console.log("all done");
// 함수를 종료하는 종료 조건!
return;
}
console.log(num);
num--; //다른인수를 입력하기 위한 인수 변경
countDown(num);
}
1
2
3
4
function factorial(num) {
if (num === 1) return 1;
return num + factorial(num -1);
}
- 내부의 재귀 return 문이 factorial(num -1)을 계속호출하게 되는데, 이때, return문은 재귀호출한 함수가 결과를 반환할 때 까지 대기 한다.
- return 문을 쓰지 않으면?
- 결과적으로 재귀로 호출하게 되는 함수의 반환값을 사용하지 못하게 된다.
- return 문은 함수 탈출의 역할 을 하기에, return을 통해 다중 재귀를통해 호출된 함수의 종료를 알려야 한다.
팩토리얼 함수 구현하기
- 일반 loop 이용
1
2
3
4
5
6
7
function factorial_loop(num) {
let total = 1;
for (let i = num; i > 0; i--) {
total *= i
}
return total;
}
- 재귀 함수이용
1
2
3
4
function factoral_reculsion(num) {
if(num === 1) return 1;
return num * factoral_reculsion(num -1);
}
재귀의 잠재적인 위험
- 종료 조건이 없는 경우
- 종료 조건이 없다면, 해당 재귀함수는 영원히 자기 자신을 호출하여 Call Stack에 함수를 쌓으므로 무한 Loop적 오류를 초래한다.
- 필요한 곳에 return (반환문) 을 사용하지 않는 경우
- 의도하지않은 코드 흐름이 발생할 수 있다.
Helper 메소드 재귀
함수 하나를 하나의 블록 개념으로 스페이스를 만들어 그안에 재귀함수를 작성하여
사용 변수를 전역으로 선언하지 않고 함수 내의 전역 변수로만 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function collectOddValues(arr) {
let result = [];
function helper (helperInput) {
if (helperInput.length === 0) {
return;
}
if (helperInput[0] % 2 !== 0) {
result.push(helperInput[0]);
}
helper(helperInput.slice(1))
}
// 재귀 함수 본격적으로 호출
helper(arr)
return result;
}
- 위 collectOddValues의 실제 논리는 내부 함수 helper에 작성이 되어 있다.
- helper함수에서 감싸고 있는 함수, 즉 외부 함수인 collectOddValues의 내부 변수 result에 결과 값을 계속 추가하는 것으로 보아 collectOddValues함수는 실질적으로 코드 스페이스의 역할을 한다.
- 이렇게 작성하면, 전역적으로 깔끔한 코드스타일을 유지 할 수 있고, wrapper함수내부의 환경에 신경쓰기만 하면 되기 때문에, 일관된 코드 스타일 유지도 가능해진다.
순수 재귀 (Pure Reculsion)
위의 collectOddValues 함수를 순수 재귀
의 방식으로 작성해본다.
순수 재귀의 경우 필요한 모든 코드가 함수 자체에 포함되며 재귀적이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
function collectOddValues(arr) {
let newArr = [];
if (arr.length === 0) {
return newArr
}
if(arr[0] % 2 !== 0) {
newArr.push(arr[0])
}
newArr = newArr.concat(collectOddValues(arr.slice(1)))
return newArr
}
내부의 newArr은 함수의 실행마다 초기화 되는 것이 맞지만, 마지막 newArr할당 구문을 통해서 계속해서 재귀 호출한 함수의 반환 값을 concat 한 후 return 함으로 최종적으로는 홀수만 골라낸 배열이 완성 된다.
순수재귀와 helper 메소드 재귀 방식에 대해서는 어떤 방식 늘 옳다고 단언할 수 없다고 생각한다.
다만 각 방식에 대한 완벽한 이해를 필두로 최적의 방법을 채택하여 작성하는 것이 옳다고 생각한다.
너무 당연한 소리인가??
연습문제
숫자를 인수로 받아 피보나치 수열의 n 번째 숫자를 반환하는 함수 작성
내가 작성한 답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function fib(num){
// add whatever parameters you deem necessary - good luck!
let arr = []
let temp = 1;
function helper(helperInput){
console.log(arr)
if(helperInput === 0) return;
if(arr.length === 0) {
arr.push(1);
} else if(arr.length === 1) {
arr.push(arr[0]);
}else if(arr.length > 1) {
arr.push(arr[arr.length - 1] + arr[arr.length - 2])
}
helper(helperInput - 1)
}
helper(num)
return arr[num-1]
}
강의의 답
1
2
3
4
function fib(n){
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.