When will you grow up?

dynamic programming 본문

02. Study/Algorithm

dynamic programming

미카이 2020. 11. 3. 11:08

다이나믹 프로그래밍
- 다이나믹 프로그래밍은 메모리를 적절히 사용하여 수행 시간 효율성을 비약적으로 향상시키는 방법입니다.
- 이미 계산된 결과(작은 문제)는 별도의 메모리 영역에 저장하여 다시 계산하지 않도록 합니다. (메모이제이션)
- 다이나믹 프로그래밍의 구현은 일반적으로 두 가지 방식(top-down, bottom-up)으로 구성됩니다.

또한, 다이나믹 프로그래밍은 동적 계획법이라고 부르며,
일반적인 프로그래밍 분야에서의 자료구조에서 동적 할당(Dynamic Allocation)은 '프로그램이 실행되는 도중에 실행에 필요한 메모리를 할당하는 기법'이라는 의미를 가지며 다이나믹 프로그래밍에 동적(Dynamic)은 별다른 의미 없이 사용된 단어라 헷갈리면 안된다.

다이나믹 프로그래밍은 문제가 다음의 조건을 만족할 때 사용할 수 있습니다.
1. 최적 부분 구조(Optimal Substructure)
  * 큰 문제를 작은 문제로 나눌 수 있으며 작은 문제의 답을 모아서 큰 문제를 해결할 수 있습니다.
2. 중복되는 부분 문제(Overlapping Subproblem)
  * 동일한 작은 문제를 반복적으로 해결해야 합니다.


대표적으로 피보나치 수열이 있다.
1,1,2,3,4,5,8,13,21,34,55,89 ...
점화식이란 인접한 항들 사이의 관계식을 의미한다.
피보나치 수열을 점화식으로 표현하는 다음과 같다.
a(n) = a(n-1) + a(n-2), a(1)=1, a(2)=1


def fibo(x):
    if x == 1 or x == 2:
        return 1
    else:
        return fibo(x-1) + fibo(x-2)
print(fibo(3))

위 처럼 단순 재귀 함수로 피보나치 수열을 해결하면 지수 시간 복잡도를 가지게 된다
ex) fibo(6) 를 호출하게 되면, f(2) 가 5번 호출된다. [중복되는 부분 문제]

중복 되는 함수 호출을 막기위해 메모이제이션(memoization)을 이용해서 해결 가능.
top-down 방식으로 / 한 번 계산한 결과를 메모리 공간에 메모하는 기법이고, 값을 기록해 놓는다는 점에서 캐싱(caching)이라고도 한다.

top-down VS bottom-up
탑다운(메모이제이션) 방식은 하향식이라고도 하며 바텀업 방식은 상향식이라고도 한다.
보통 전형적인 형태는 바텀업 방식이고, 결과 저장용 리스트는 DP 테이블이라고도 부른다.
엄밀히 말하면, 메모이제이션은 이전에 계산된 결과를 일시적으로 기록해 놓은 넓은 개념을 의미해 DP에만 국한된 개념은 아니다!

# 메모이제이션 방식(top-down)으로 피보나치 구현
# 아래 시간 복잡도는 O(N) 이다. 이유는 생각해보자.
d = [0] * 100 # 최대 100까지만 계산을 위해 리스트 초기화
def fibo(x):
    # print('f('+str(x)+')', end=' ')
    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))


# bottom-up 방식으로 피보나치 구현
d = [0] * 100

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])


다이나믹 프로그래밍(DP) VS  분할 정복(divide and conquer)
- 다이나믹 프로그래밍과 분할 정복은 모두 최적 부분 구조를 가질 때 사용할 수 있다
- dp와 분할 정복의 차이점은 부분 문제의 중복이라 dp 에선 각 부분 문제들이 서로 영향을 미치고 분할 정복에서는 동일한 부분 문제가 반복적으로 계산되지 않는다.

이게 생각보다 어려운데,
- DP 유형임을 파악하는게 중요한데, 가장 먼저 그리디, 완탐, 등의 아이디어로 문제를 해결할 수 있는지 검토하고 풀이 방법이 떠오르지 않는다면 다이나믹 프로그래밍을 고려
- 일단 재귀 함수로 비효율적인 완탐을 작성하고 작은 문제에서 구한 답이 큰 문제에서 그대로 사용될 수 있으면 코드를 개선하는 방법 사용
- 일반적인 코테 수준에서는 기본 유형의 DP 문제가 출제되서 쫄지 않아도 된다. 반복적인 연습을 통해서 이런유형을 익숙해질 수 있다




<문제> 개미전사
# 개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 한다. 메뚜기 마을에는 여러 개의 식량 창고가 있는데 식량창고는 일직선으로 이어져 있다.
# 각 식량창고에는 정해진 수의 식량을 저장하고 있으며 개미 전사는 식량창고를 선택적으로 약탈하여 식량을 빼앗을 예정입니다. 이때 메뚜기 정찰병들은 일직선상에 존재하는 식량창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있다.
# 따라서 개미 전사가 정찰병에게 들키지 않고 식량창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량창고를 약탈해야 한다.
예)
창고(0)  창고(1)  창고(2)  창고(3)
  1          3         1          5
# 이때 개미 전사는 두 번째 식량창고와 네 번째 식량창고를 선택했을 때 최댓값인 8개의 식량을 빼앗을 수 있다.
# 개미 전사를 위해 식량창고 N개에 대한 정보가 주어졌을 때 얻을 수 있는 식량의 최댓값을 구하는 프로그램을 작성

# 입력 예시
# 첫째 줄에 식량창고의 개수 N이 주어집니다 (3 <= N <= 100)
# 둘째 줄에 공백을 기준으로 각 식량창고에 저장된 식량의 개수 K가 주어집니다 (0<=K<=1000)
4
1 3 1 5
출력
8


# 점화식
# ai=i번째 식량창고까지의 최적의 해 (얻을 수 있는 식량의 최댓값)
# ki=i번째 식량창고에 있는 식량의 양
# ai = max(ai-1, ai-2+ki)


n = int(input())
k = list(map(int, input().split(' ')))

# 앞서 계산된 결과를 저장하기 위한 dp 테이블 초기화
d_table = [0] * 100


d_table[0] = k[0]
d_table[1] = max(k[0], k[1])
for i in range(2, n):
    d_table[i] = max(d_table[i-1], d_table[i-2]+k[i])
    
print(d_table[n-1])

 

 

위 내용들은 이코테 youtube 및 책을 참고하였습니다

이코테 링크

 링크

'02. Study > Algorithm' 카테고리의 다른 글

binary search  (0) 2020.10.11
Sort Algorithm  (0) 2020.10.11
DFS / BFS  (0) 2020.10.09
Greedy Algorithm & Implementation  (0) 2020.10.07
python_그래프 기초  (0) 2019.09.02
Comments