-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* github actions 테스트 목적
- Loading branch information
Showing
1 changed file
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
--- | ||
title: 'This' | ||
date: '2024-01-08' | ||
spoiler: '자바스크립트의 this란?' | ||
--- | ||
|
||
## Contents | ||
|
||
# 재귀함수 | ||
|
||
재귀함수는 정의 단계에서 자기 자신을 재참조 하는 함수로서, 트리, 트라이의 자동완성, dfs 기타 자료구조 및 알고리즘을 풀 때 빼놓을 수 없는 개념이다. | ||
|
||
재귀함수를 그동안 제대로 이해하려고 하지 않고, 어물쩡~~ 저물쩡~~ 예제코드가 어쩌구~~ 뭐쩌구~~ 하면서 넘어갔지만 더이상은 안되겠다는 생각이 들어서 오늘 정리를 한번 해보려고 한다. | ||
 | ||
|
||
> 더는 미룰 수 없다. | ||
# 재귀함수의 구성 | ||
|
||
재귀함수는 두가지의 부분으로 구분 할 수 있다. | ||
|
||
1. 재귀를 하면서 원하는 요구를 수행하는 부분 | ||
2. 종료 조건을 표시한 부분 | ||
|
||
여기서 재귀함수를 이미 알고있는 사람은 1~10까지 더하는 함수를 재귀적으로 나타내어 보자. 재귀함수를 잘 모르는 사람은 아래의 코드를 확인해보자. | ||
|
||
```js | ||
//일반 함수로 구현 | ||
function recursive_plus(num, sum) { | ||
//재귀함수가 끝나는 조건을 표시 | ||
if (num == 0) return sum; | ||
//재귀를 하면서 실행될 로직 | ||
sum += num; | ||
//자기 자신을 다시 쓰는, 재귀를 하는 부분 | ||
return recursive_plus(num - 1, sum); | ||
} | ||
console.log(recursive_plus(10, 0)); //55 | ||
|
||
//사실 이렇게도 할 수 있다. | ||
function recursive_plus(num, sum) { | ||
if (num <= 1) return num; | ||
return num + recursive_plus(num - 1, sum); | ||
} | ||
|
||
//클래스로 구현 | ||
class recursive_plus { | ||
constructor() { | ||
this.sum = 0; | ||
} | ||
recursive_logic(num) { | ||
//재귀함수가 끝나는 조건을 표시 | ||
if (num == 0) return this.sum; | ||
//재귀를 하면서 실행될 로직 | ||
this.sum += num--; | ||
//자기 자신을 다시 쓰는, 재귀를 하는 부분 | ||
this.recursive_logic(num); | ||
} | ||
} | ||
const SUM = new recursive_plus(); | ||
SUM.recursive_logic(10); | ||
console.log(SUM.sum); //55 | ||
``` | ||
|
||
이런 식으로 작성을 할 수 있다. | ||
|
||
재귀함수를 실행하면 지역변수, 반환주소값, 매개변수가 콜스택에 차례차례 쌓이게 된다. | ||
 | ||
재귀함수가 실행될때, 콜스택이란 곳에 함수가 차곡차곡 push되어서 종료조건까지 실행이 끝나면 하나씩 pop된다고 생각하면 된다. | ||
|
||
이해를 편하게 하기위해, 위의 두번째 더하기 함수가 | ||
|
||
```js | ||
return num+(num-1)+(num-2)+(num-3)+...+2+1; | ||
``` | ||
|
||
이렇게 동작한다고 보면 된다. | ||
|
||
## 앞구르기~ 뒷구르기~ | ||
|
||
재귀함수도 재귀함수를 앞에 둘 수도 있고, 뒤에 둘 수 있다. | ||
|
||
```js | ||
//종료조건에 닿으면 재귀 종료 | ||
function recursive_plus_front(num, sum) { | ||
if (num <= 1) return num; | ||
return num + recursive_plus_front(num - 1, sum); | ||
} | ||
//종료조건까지 재귀 실행 | ||
function recursive_plus_back(num, sum) { | ||
if (num > 1) return num + recursive_plus_back(num - 1, sum); | ||
return 1; | ||
} | ||
console.log(recursive_plus_front(10, 0)); //55 | ||
console.log(recursive_plus_back(10, 0)); //55 | ||
``` | ||
|
||
취향과 상황에 맞게 골라드시면 되겠습니다. 아니 그냥 둘 다 연습을 하십셔 | ||
|
||
## 종료조건 정의 | ||
|
||
여기서 중요한 부분은 종료부분이라고 생각을 한다. | ||
자기가 어떤 조건을 만족할 때 재귀를 종료하고 return을 뭘 시킬지를 생각을 해보는게 좋을 것이다. | ||
|
||
만약 그렇지 않는다면 꼬리에 꼬리를 무는 함수... | ||
꼬꼬무 함수의 탄생을 마주하게 될것이다. | ||
 | ||
|
||
> 종료조건을 잘 작성하지 못한다면 무한루프에 빠질 수 있다. | ||
## 종료조건 만들기 | ||
|
||
그리고 이럴 때 재귀를 종료합니다~~ 라고만 하면 안되고, 함수안에서 다시 함수를 부를때, 인자가 변화를 해야한다. 위의 함수에서는 num을 계속 1씩 빼면서 종료조건에 닿게 만들어준다. | ||
|
||
# 꼬리재귀 | ||
|
||
tail_call recursive function이라고 부르는데, 일반재귀함수와 다른점이 있다. | ||
연산부를 바로 리턴에 보내버린다는 점인데 아래의 코드를 확인해보자. | ||
|
||
```js | ||
let t1 = performance.now(); | ||
function recursive_plus(num, sum) { | ||
if (num == 1) return num; | ||
sum += num; | ||
return num + recursive_plus(num - 1, sum); | ||
} | ||
let t2 = performance.now(); | ||
|
||
let t3 = performance.now(); | ||
function tail_call_recursive_plus(num, sum) { | ||
if (num == 1) return sum + 1; | ||
return tail_call_recursive_plus(num - 1, sum + num); | ||
} | ||
let t4 = performance.now(); | ||
console.log(recursive_plus(100, 0), `time : ${t2 - t1}ms`); | ||
console.log(tail_call_recursive_plus(100, 0), `time : ${t4 - t3}ms`); | ||
``` | ||
|
||
실행을 해보면 속도차이가 10배 넘게 차이가 나는것을 알 수 있다. | ||
메모리를 덜 사용한다고 한다. | ||
|
||
# 사실은 반복문 써도 똑같음 | ||
|
||
재귀함수가 당장은 필요하지 않다고 생각할 수 있다. 알고리즘 문제에서 사용하는거면 몰라도, 콜스택이 흘러넘치는 문제(`스택오버플로우`)를 감수하면서까지 코드를 짤 때, 반복문 대신에 재귀를 사용할 필요가 있을까? | ||
|
||
내 생각에 우리는 결국 나중에 규모가 점점 커지는 프로젝트들을 할텐데, 조금 더 코드를 직관적이고, 간결하게 알아볼 수 있게 해주는데 의의가 있는것 같다. 당장에 dfs를 재귀로 구현한 코드만 보더라도 재귀에 익숙하다면 지금 어떤 코드를 짜버린건지 이해를 하기가 쉽다. | ||
|
||
코딩을 하다 보면 콜백함수도 자주 넣게 되는데, 이런, 함수와 함수의 상호작용을 다루는 부분도 재귀함수와 비슷한 부분이 많은 것 같다. 실용적인 부분은 차치하더라도, 조금 더 프로그래머처럼 사고하는데 그 의미가 있다고 생각한다! | ||
|
||
 | ||
|
||
> 그래도 재귀함수 사랑하시죠...? ^^ | ||
### 이미지 출처 | ||
|
||
https://www.geeksforgeeks.org/ |