이것이 취업을 위한 코딩 테스트다 with 파이썬을 공부하면서 정리한 내용입니다.


중복되는 연산 줄이기

  • 메모리 공간을 더 사용하면서 연산 속도를 비약적으로 증가시킬 수 있는 방법이 있음
  • 대표적인 방법이 다이나믹 프로그래밍 기법으로 동적 계획법으로 표현하기도 함
  • 다이나믹 프로그래밍으로 해결할 수 있는 대표적인 예시로 피보나치 수열이 있음
  • 피보나치 수열은 이전 두 항의 합을 현재의 항으로 설정하는 수열
  • 점화식은 인접한 항들 사이의 관계식을 의미하며 피보나치 수열의 점화식은 다음과 같음
    an+2=f(an+1,an)=an+1+ana_{n+2} = f(a_{n+1}, a_n) = a_{n+1} + a_n
  • 피보나치 수열에서 첫 번째 항과 두 번째 항의 값은 모두 1이기 때문에 최종적으로 피보나치 수열을 나타낼 때에는 다음과 같이 정의
    an=an1+an2,a1=1,a2=1a_n = a_{n-1} + a_{n-2}, a_1 = 1, a_2 = 1
  • 프로그래밍에서는 수열을 배열이나 리스트로 표현할 수 있음
  • 수학적 점화식을 프로그래밍으로 표현할 때 재귀 함수를 사용하면 간단
  • 피보나치 함수는 다음과 같이 작성할 수 있음
def fibo(x):
    if x == 1 or x == 2:
        return 1
    return fibo(x - 1) + fibo(x - 2)

print(fibo(4))
3
  • 그런데 피보나치 수열의 소스 코드를 위와 같이 작성하면 f(n)f(n) 함수에서 nn이 커질수록 수행 시간이 기하급수적으로 늘어나는 문제 발생
  • 일반적으로 피보나치 수열은 빅오 표기법을 이용하면 O(2N)O(2^N)이 소요된다고 표현
  • f(6)f(6)일 때의 호출 과정을 그림으로 나타내면 다음과 같음

  • 그림을 보면 동일한 함수가 반복적으로 호출되는 것을 알 수 있음
  • f(n)f(n)에서 nn이 커지면 커질수록 반복해서 호출하는 수가 많아짐
  • 피보나치 수열의 점화식을 재귀 함수를 사용해 만들 수는 있지만, 단순히 매번 하도록 하면 문제를 효율적으로 해결할 수 없음
  • 이러한 문제는 다이나믹 프로그래밍을 사용하면 효율적으로 해결할 수 있음
  • 다이나믹 프로그래밍은 다음 조건을 만족할 때 사용할 수 있음
    1. 큰 문제를 작은 문제로 나눌 수 있음
    2. 작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일함
  • 피보나치 수열은 다이나믹 프로그래밍의 조건을 만족함
  • 다이나믹 프로그래밍을 구현하는 방법 중 메모이제이션이 있는데, 한 번 구한 결과를 메모리 공간에 메모해두고 같은 식을 다시 호출하면 메모한 결과를 그대로 가져오는 기법
  • 메모이제이션을 구현할 때는 한 번 구한 정보를 리스트에 저장하고 다이나믹 프로그래밍을 재귀적으로 수행하다가 같은 정보가 필요하면 리스트에서 값을 가져오면 됨
# 한 번 계산된 결과를 메모이제이션(Memoization)하기 위한 리스트 초기화
d = [0] * 100

# 피보나치 함수를 재귀 함수로 구현(탑다운 다이나믹 프로그래밍)
def fibo(x):
    # 종료 조건(1 또는 2일 때 1을 반환)
    if x == 1 or x == 2:
        return 1
    # 이미 계산한 적 있는 문제라면 그대로 반환
    if d[x] != 0:
        return d[x]
    # 아직 계산하지 않은 문제라면 점화식에 따라서 피보나치 결과 반환
    d[x] = fibo(x - 1) + fibo(x - 2)
    return d[x]

print(fibo(99))
218922995834555169026
  • 위 소스 코드를 실행하면 99번째 피보나치 수를 구하도록 했음에도 빠르게 정답을 도출
  • 다이나믹 프로그래밍을 적용했을 때의 피보나치 수열 알고리즘의 시간 복잡도는 O(N)O(N)
  • 다이나믹 프로그래밍이란 큰 문제를 작게 나누고, 같은 문제라면 한 번씩만 풀어 문제를 효율적으로 해결하는 알고리즘 기법
  • 다이나믹 프로그래밍과 분할 정복의 차이점은 다이나믹 프로그래밍은 문제들이 서로 영향을 미치고 있다는 점
  • 재귀 함수를 이용하여 다이나믹 프로그래밍 소스 코드를 작성하는 방법은, 큰 문제를 해결하기 위해 작은 문제를 호출한다고 하여 탑다운 방식이라고 함
  • 재귀 함수를 사용하면 컴퓨터 시스템에서는 함수를 다시 호출했을 때 메모리에 적재되는 일련의 과정을 따라야 하므로 오버헤드가 발생할 수 있음
  • 재귀 함수 대신에 반복문을 사용한다면 오버헤드를 줄일 수 있음
  • 일반적으로 반복문을 이용한 다이나믹 프로그래밍이 더 성능이 좋음
  • 반복문을 이용하여 다이나믹 프로그래밍 소스 코드를 작성하는 경우, 작은 문제부터 차근차근 답을 도출한다고 하여 바텀업 방식이라고 함
  • 피보나치 수열 문제를 바텀업 방식으로 풀면 다음과 같음
# 앞서 계산한 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 100

# 첫 번째 피보나치 수와 두 번째 피보나치 수는 1
d[1] = 1
d[2] = 1
n = 99

# 피보나치 함수를 반복문으로 구현(바텀업 다이나믹 프로그래밍)
for i in range(3, n + 1):
    d[i] = d[i - 1] + d[i - 2]

print(d[n])
218922995834555169026
  • 탑다운(메모이제이션) 방식은 하향식이라고도 하며, 바텀업 방식은 상향식이라고도 함
  • 다이나믹 프로그래밍의 전형적인 형태는 바텀업 방식
  • 바텀업 방식에서 사용되는 결과 저장용 리스트는 DP 테이블이라고 부르며, 메모이제이션은 탑다운 방식에 국한되어 사용되는 표현
  • 다이나믹 프로그래밍과 메모이제이션의 개념을 혼용해서 사용하는 경우도 있는데, 엄밀히 말하면 메모이제이션은 이전에 계산된 결과를 일시적으로 기록해 놓은 넓은 개념을 의미하므로, 다이나믹 프로그래밍과는 별도의 개념
  • 한 번 계산된 결과를 어딘가에 담아 놓기만 하고 다이나믹 프로그래밍을 위해 활용하지 않을 수도 있음
  • 수열은 배열이나 리스트로 표현할 수 있다고 했는데, 메모이제이션은 경우에 따라서 딕셔너리와 같이 다른 자료형을 이용할 수도 있음
  • 딕셔너리는 수열처럼 연속적이지 않을 때 유용한데, 예를 들어 ana_n을 계산할 때 a0an1a_0 \sim a_{n-1} 모두가 아닌 일부의 작은 문제에 대한 해답만 필요한 경우가 있음
profile
전부인 것처럼, 전부가 아닌 것처럼

0개의 댓글