다이나믹 프로그래밍의 두 가지 방식
다이나믹 프로그래밍은 두 가지 방식으로 구현된다.
Top-Down (재귀적) : 큰 문제를 작은 문제로 접근 - 재귀 + 메모이제이션 사용
Bottom-Uo (반복적) : 작은 문제를 결합해 큰 문제로 - 테이블로 단계적으로 쌓아감
재귀함수는 다이나믹 프로그래밍의 Top-Down 방식에 해당한다.
예제 - 피보나치수
피보나치수의 정의
1
2
3
fib ( 0 ) = 0
fib ( 1 ) = 1
fib ( n ) = fib ( n - 1 ) + fib ( n - 2 )
재귀함수
재귀함수의 정의
자기 자신을 다시 호출하는 함수
즉, 함수 내부에서 자기 자신을 호출해, 문제를 더 작은 단위로 해결하려는 방식
재귀함수의 필수 구성
종료 조건 (Base Case) : 더 이상 재귀 호출을 하지 않고 끝나는 조건이 있어야 한다.
재귀 호출 (Recursive Case) : 자기 자신을 다시 호출하는 부분이 있어야 한다.
피보나치 수열을 재귀함수로 정의하기
1
2
3
4
def fib ( n ):
if n <= 1 :
return n
return fib ( n - 1 ) + fib ( n - 2 )
메모이제이션
메모이제이션의 정의
어떤 함수에 들어왔던 파라미터와, 그 파라미터에서의 함수 결과값을 저장하고 있다가
나중에 동일한 파라미터가 들어오면, 계산 없이 저장한 결과값을 바로 반환하는 기법
@lru_cache(maxsize=None)
어노테이션을 통해 메모이제이션을 함수에 적용한다.
1
2
3
4
5
6
from functools import lru_cache
@ lru_cache ( maxsize = None )
def some_function ( param ):
...
return a
메모이제이션 미적용 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time
start = time . time ()
def fib ( n ):
if n <= 1 :
return n
return fib ( n - 1 ) + fib ( n - 2 )
# 시간 측정
print ( fib ( 40 ))
print ( f 'duration : { time . time () - start } ' )
# 출력
>> 102334155
>> duration : 8.589844226837158
메모이제이션 적용 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from functools import lru_cache
import time
start = time . time ()
@ lru_cache ( maxsize = None )
def fib ( n ):
if n <= 1 :
return n
return fib ( n - 1 ) + fib ( n - 2 )
# 시간 측정
print ( fib ( 50 ))
print ( f 'duration : { time . time () - start } ' )
# 출력
>> 102334155
>> duration : 0.0007688999176025391
dict 를 이용한 직접 메모이제이션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
start = time . time ()
memo = {}
def fib_manual ( n ):
if n in memo :
return memo [ n ]
if n <= 1 :
memo [ n ] = n
else :
memo [ n ] = fib_manual ( n - 1 ) + fib_manual ( n - 2 )
return memo [ n ]
# 시간 측정
print ( fib_manual ( 40 ))
print ( f 'duration : { time . time () - start } ' )
# 출력
>> 102334155
>> duration : 0.0006320476531982422
재귀 호출 한도 증대
재귀 호출 한도
하나의 함수가 자신을 반복 호출할 수 있는 최대 깊이를 말한다.
깊이란, 스택에 쌓이는 함수 호출의 횟수이다.
너무 깊게 재귀 호출을 하면 스택 오버플로 혹은 RecursionError 가 발생한다.
이를 방지하기 위해 대부분의 언어는 재귀 깊이에 제한을 둔다.
이 제한은 호출 스택 크기를 관리하고 메모리 폭주를 방지하기 위한 장치이다.
쉽게 말해, “몇 번까지 재귀적으로 함수를 부를 수 있나?”를 제한하는 수치이다.
파이썬의 기본 재귀 호출 한도
파이썬의 기본 재귀 호출 한도는 환경마다 다르며
sys.getrecursionlimit()
함수를 통해 확인할 수 있다.
1
2
3
4
import sys
print ( sys . getrecursionlimit ())
>> 3000
한도를 초과하는 재귀호출 해보기
1
2
3
4
5
6
7
8
9
10
11
from functools import lru_cache
@ lru_cache ( maxsize = None )
def fib ( n ):
if n <= 1 :
return n
return fib ( n - 1 ) + fib ( n - 2 )
print ( fib ( 3001 ))
>> RecursionError : maximum recursion depth exceeded
재귀호출 한도를 늘리는 방법
sys.setrecursionlimit()
함수를 통해 한도를 늘릴 수 있다.
이론적으로는 한도를 무한하게 늘릴 수 있으나,
현실적으로는 컴퓨터의 메모리와 시스템 스택 한도에 따라 제한된다.
너무 크게 설정하면 강제 종료나 시스템 크러시가 발생할 수도 있다.
일반적으로는 10000회 이하로 제한하는 것이 권장된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import lru_cache
import sys
sys . setrecursionlimit ( 5000 )
@ lru_cache ( maxsize = None )
def fib ( n ):
if n <= 1 :
return n
return fib ( n - 1 ) + fib ( n - 2 )
print ( fib ( 3300 ))
>> 20404587654072771. ..
Tags:
dp ,
dynamic ,
dynamicprogramming ,
lru_cache ,
programming ,
python ,
setrecursionlimit ,
다이나믹프로그래밍 ,
메모이제이션 ,
알고리즘기초 ,
알고리즘문제 ,
재귀함수 ,
재귀함수예제 ,
재귀호출한도 ,
파이썬 ,
파이썬성능개선 ,
파이썬최적화 ,
피보나치 ,
해결방법
Categories:
Algorithm
Updated: 2025-07-d
Comments