꼬리재귀로 이항계수 구현하기
$$ {n\choose k} = \frac{n!}{k!(n-k)!} $$
이항계수의 공식.
얼추 코드로 옮기면 아래처럼 된다. $(0 \le k \le n)$
int
일단 재귀로 바꿔보자...
f(n, 0)
은...
$$ f(n,0) = \frac{n!}{0!n!} = \frac{n!}{n!} = 1 $$
int
f(n, k)
는...
$$ f(n,k) = \frac{n!}{k!(n-k)!} = \frac{(n-1)!}{(k-1)!(n-k)!} \times \frac{n}{k} = f(n-1,k-1) \times \frac{n}{k} $$
int
완성...
이제 이걸 꼬리재귀로 바꿔야 하는데...
함수 반환값을 즉시 반환해야하니까... 반환값에 n
을 곱하고 k
로 나누는 연산을 없애야 한다.
위 코드에서 결과값이 담긴 인자를 추가하고 마지막에 결과값을 반환하도록 바꾸면...
int
그런데 이 코드는 제대로 된 값을 내놓을 수 없다... f(5, 2)
를 호출하면 f(4, 1, 2)
를 호출하고, 또 얘는 f(3, 0, 8)
을 호출한다... 10이 나와야 되는데...ㅜㅜ
그래서 인자를 하나 더 추가했다. 나누기 연산을 마지막에 하기 때문에 위 코드와 같은 문제가 생기지 않는다.
int
a
와 b
에 계속 곱했다가 마지막에 나누는 방법인데... 값을 조금만 크게 줘도 오버플로우가 생긴다...
이제 꼬리재귀가 제대로 적용되는지 확인해보자...
bc.cpp
(꼬리재귀X)랑 bc_tail.cpp
(꼬리재귀O)를 만들어서 실험해봤다.. 둘다... -O2 -g
옵션 줘서... 컴파일함...
bc.cpp
int
int
bc_tail.cpp
int
int
gef> info stack
#0 f (n=50, k=0) at bc.cpp:4
#1 0x00005555555549d5 in f (n=51, k=1) at bc.cpp:7
#2 0x00005555555549d5 in f (n=52, k=2) at bc.cpp:7
#3 0x00005555555549d5 in f (n=53, k=3) at bc.cpp:7
#4 0x00005555555549d5 in f (n=54, k=4) at bc.cpp:7
#5 0x00005555555549d5 in f (n=55, k=5) at bc.cpp:7
#6 0x00005555555549d5 in f (n=56, k=6) at bc.cpp:7
#7 0x00005555555549d5 in f (n=57, k=7) at bc.cpp:7
#8 0x00005555555549d5 in f (n=58, k=8) at bc.cpp:7
#9 0x00005555555549d5 in f (n=59, k=9) at bc.cpp:7
...
#41 0x00005555555549d5 in f (n=91, k=41) at bc.cpp:7
#42 0x00005555555549d5 in f (n=92, k=42) at bc.cpp:7
#43 0x00005555555549d5 in f (n=93, k=43) at bc.cpp:7
#44 0x00005555555549d5 in f (n=94, k=44) at bc.cpp:7
#45 0x00005555555549d5 in f (n=95, k=45) at bc.cpp:7
#46 0x00005555555549d5 in f (n=96, k=46) at bc.cpp:7
#47 0x00005555555549d5 in f (n=97, k=47) at bc.cpp:7
#48 0x00005555555549d5 in f (n=98, k=48) at bc.cpp:7
#49 0x00005555555549d5 in f (n=99, k=49) at bc.cpp:7
#50 0x00005555555549d5 in f (n=50, k=100) at bc.cpp:7
#51 main () at bc.cpp:14
그냥 재귀로 된 건 이렇게 스택이 쌓이지만...
f(int, int, int, int):
mov eax, edx
sub edi, esi
test esi, esi
je .L3
.L2:
imul ecx, esi
lea edx, [rdi+rsi]
imul eax, edx
dec esi
jne .L2
.L3:
cdq
idiv ecx
ret
꼬리재귀 함수는 main
에서 반복문이 도는 형태로 바뀌었다. 그래서 스택이 계속 쌓이지 않는다...
ㅜㅜ...