6.2 재귀함수(Recursive function)
함수 자신을 다시 호출하는 함수로 직관적으로 이해하기 쉽고 간결함
- 재귀함수 작성 시 재귀호출을 탈출하는 조건을 명확히 하는 것이 관건
일반적 함수의 호출 및 리턴 과정
main()
함수 안에서 함수A
가 호출되면, 코드 진행은 함수A
의 처음으로 옮겨짐.- 마찬가지로 함수
A
내부에서 함수B
가 호출되면서 코드 진행은 함수B
의 처음으로 옮겨짐. - 함수
B
가 진행되면 중간에 함수C
가 호출되면서 함수C
의 처음으로 진행이 옮겨짐 - 함수
C
가 모든 실행을 마치면 함수B
에서C
를 호출했던 다음 줄로 돌아감(return) - 함수
B
의 모든 실행을 마치면 함수A
에서B
를 호출했던 다음 줄로 돌아감(return) - 함수
A
의 모든 실행을 마치면main()
함수에서A
를 호출했던 다음 줄로 돌아감(return)
재귀 함수의 호출 및 리턴 과정
- 모든 재귀함수의 호출 시 새로운 작업공간(메모리)을 확보해 진행
- 동일한 코드가 작업공간만 옮겨 다니며 무한히 반복되는 구조이기 때문에 탈출조건이 필요
예제1: 재귀함수를 이용한 1부터 n
까지 합을 구하는 함수
<- function(n) {
recursive_sum if (n == 1) return(n) # 종료 조건
return(n + recursive_sum(n-1))
}
recursive_sum(3)
[1] 6
recursive_sum(3)
실행 시n
이 1이 아니기 때문에recursive_sum(2)
호출recursive_sum(2)
실행 시n
이 1이 아니기 때문에recursive_sum(1)
호출recursive_sum(1)
이면n == 1
을 만족하기 때문에 1 반환(return)recursive_sum(2)
는recursive_sum(1)
에서 반환 받은 1과n = 2
을 더해서 3을 반환(return)recursive_sum(3)
은recursive_sum(2)
에서 반환 받은 3과n = 3
을 더해서 6을 반환 \(\rightarrow\) 종료
예제2: 계승(factorial) 계산하기
\[ n! = \begin{cases} n \times (n - 1)!, & n=1, \cdots \\ 1, & n = 0 \end{cases} \]
\(f(n) = n!\) 이라고 하면 \(f(n)\)은 아래와 같이 나타낼 수 있음.
\[ n! = \begin{cases} n \times f(n-1), & n=1, \cdots \\ 1, & n = 0 \end{cases} \]
위 식을 이용해 \(3!\)을 구하는 과정: - \(f(3) = 3\times f(2) = 3\times 2 \times f(1) = 3 \times 2\times 1\times f(0) = 3\times 2\times 1\times 1 = 6\)
f(3) = 3*f(2)
f(2) = 2 * f(1)
f(1) = 1
위 과정을 함수로 구현
<- function(n) {
factorial_manual # browser()
if (n == 0) return(1)
return(n * factorial_manual(n-1))
}
# test
factorial_manual(3)
[1] 6
factorial_manual(10)
[1] 3628800
# R 내장함수로 검증
factorial(10)
[1] 3628800
확장예제: 하노이 탑(tower of Hanoi)
“인도 바라나시에 있는 한 사원에는 세상의 중심을 나타내는 큰 돔이 있고 그 안에 세 개의 다이아몬드 바늘이 동판 위에 세워져 있습니다. 바늘의 높이는 1 큐빗이고 굵기는 벌의 몸통만 합니다. 바늘 가운데 하나에는 신이 64개의 순금 원판을 끼워 놓았습니다. 가장 큰 원판이 바닥에 놓여 있고, 나머지 원판들이 점점 작아지며 꼭대기까지 쌓아 있습니다. 이것은 신성한 브라흐마의 탑입니다. 브라흐마의 지시에 따라 승려들은 모든 원판을 다른 바늘로 옮기기 위해 밤낮 없이 차례로 제단에 올라 규칙에 따라 원판을 하나씩 옮깁니다. 이 일이 끝날 때, 탑은 무너지고 세상은 종말을 맞이하게 됩니다.”
Wikipedia 발췌
문제: 3개의 기둥 A, B, C가 있고, 기둥 A에 \(N\) 개의 원판이 크기 순서대로 쌓여져 있을 때(제일 밑에 원판이 가장 큼), 모든 원판을 기둥 C로 옮기기
조건
- 한 번에 하나의 원판만 옮길 수 있음
- 큰 원판이 작은 원판 위에 있으면 안됨
Solution
원판의 크기가 제일 작은 것 부터 큰 것 까지 각각 1, 2, 3 번을 부여 했을 때
- 1 번 원판을 봉 A에서 C로 옮김 (A \(\rightarrow\) C)
- 2 번 원판을 봉 A에서 B로 옮김 (A \(\rightarrow\) B)
- 1 번 원판을 봉 C에서 B로 옮김 (C \(\rightarrow\) B)
- 3 번 원판을 봉 A에서 C로 옮김 (A \(\rightarrow\) C)
- 1 번 원판을 봉 B에서 A로 옮김 (B \(\rightarrow\) A)
- 2 번 원판을 봉 B에서 C로 옮김 (B \(\rightarrow\) C)
- 1 번 원판을 봉 A에서 C로 옮김 (A \(\rightarrow\) C)
원판이 3개인 경우 총 7번의 이동이 필요 \(\rightarrow\) \(n\)개의 원판이 있을 경우 \(2^n - 1\) 번의 이동이 필요
알고리즘 구현
<- function(k, from, to, via) {
move_hanoi # browser()
if (k == 1) {
print(sprintf("%d 번 원판을 %s 에서 %s 로 이동", 1, from, to))
else {
} move_hanoi(k - 1, from = from, to = via, via = to)
print(sprintf("%d 번 원판을 %s 에서 %s 로 이동",
from = k,
to = from,
via = to))
move_hanoi(k - 1, from = via, to = to, via = from)
}
}
move_hanoi(3, "A", "C", "B")
[1] "1 번 원판을 A 에서 C 로 이동"
[1] "2 번 원판을 A 에서 B 로 이동"
[1] "1 번 원판을 C 에서 B 로 이동"
[1] "3 번 원판을 A 에서 C 로 이동"
[1] "1 번 원판을 B 에서 A 로 이동"
[1] "2 번 원판을 B 에서 C 로 이동"
[1] "1 번 원판을 A 에서 C 로 이동"
move_hanoi(4, "A", "C", "B")
[1] "1 번 원판을 A 에서 B 로 이동"
[1] "2 번 원판을 A 에서 C 로 이동"
[1] "1 번 원판을 B 에서 C 로 이동"
[1] "3 번 원판을 A 에서 B 로 이동"
[1] "1 번 원판을 C 에서 A 로 이동"
[1] "2 번 원판을 C 에서 B 로 이동"
[1] "1 번 원판을 A 에서 B 로 이동"
[1] "4 번 원판을 A 에서 C 로 이동"
[1] "1 번 원판을 B 에서 C 로 이동"
[1] "2 번 원판을 B 에서 A 로 이동"
[1] "1 번 원판을 C 에서 A 로 이동"
[1] "3 번 원판을 B 에서 C 로 이동"
[1] "1 번 원판을 A 에서 B 로 이동"
[1] "2 번 원판을 A 에서 C 로 이동"
[1] "1 번 원판을 B 에서 C 로 이동"