4학년 1학기 전공/알고리즘
[알고리즘] Chapter 3. 동적계획(Dynamic Programming)
시데브
2025. 4. 21. 01:01
경희대학교 한치근 교수님의 알고리즘 수업을 기반으로 정리한 글입니다.
Dynamic Programming
- divide-and-conquer(분할정복식, 재귀): top-down 해결법
- Dynamic Programming: bottom-up 해결법
- 작은 문제 해결 먼저 -> 결과를 큰 문제의 해결로 확산
- 재귀 관계식(recursive property) 정립
- 작은 사례를 먼저 해결하는 상향식 방법으로 진행
- 작은 문제 해결 먼저 -> 결과를 큰 문제의 해결로 확산
1. 이항계수 구하기

- 문제: 이항계수를 계산
- 입력: 음수가 아닌 정수 n과 k, 여기서 k <= n
- 출력: bin, nCk

- 시간복잡도(분할정복식 접근방법)
- 귀납증명에 따라, nCk를 구하기 위해서 알고리즘이 계산하는 항(term)의 개수는 2*nCk - 1
- 동적계획법 알고리즘 설계전략
- recursive property 정립
- nCk를 구하기 위해서 B[0][0]부터 시작하여 위에서 아래로 재귀 관계식을 적용하여 배열을 채워 나가면 됨


- 총 수행횟수는 ∈ Θ(nk)
2. 최단경로 문제(all-pairs shortest paths problem)
- 그래프 용어
- 정점(vertex, node), 이음선(edge, arc)
- 방향 그래프(directed graph/digraph)
- 가중치(weight), 가중치 포함 그래프(weighted graph)
- 경로(path): 각 정점에서 다음 정점을 잇는 이음선이 존재하는 일련의 정점들
- 단순경로(simple path): 같은 정점을 두 번 지나지 않는 경로
- 순환(cycle): 한 정점에서 다시 그 정점으로 돌아오는 경로
- 순환 그래프(cyclic graph) vs 비순환 그래프(acyclic graph)
- cycle이 있는 그래프 vs 없는 그래프
- 길이(length): 경로상에 있는 가중치의 합(가중치 포함 그래프). 경로상의 이음선의 개수(가중치가 없는 그래프)
2.1. all-pairs shortest paths problem
- 문제: 가중치 포함, 방향성 그래프에서 최단경로 찾기
- 최적화문제(optimization problem)
- 주어진 문제에 대하여 하나 이상의 많은 해답이 존재할 때, 이 가운데에서 optimal solution을 찾아야 하는 문제
- Adjacency Martix: W

- Brute-force algorithm
- 한 정점에서 다른 정점으로의 가능한 모든 경로들의 길이를 구한 뒤, 그들 중에서 최소 길이의 경로를 탐색
- 분석
- n개의 정점이 모두 연결된 상태
- 처음에 선택 가능한 정점 n-2개, n-1개, ...
- 총 경로의 개수는 (n-2)! -> 지수함수보다 큰 시간복잡도 비효율적!
- Dynamic Programming
- case 1: { v1, v2, .. ,vk }의 정점들만을 통해서, v_i에서 v_j로 가는 최단경로가 v_k를 거치지 않는 경우
- case 2: { v1, v2, .. ,vk }의 정점들만을 통해서, v_i에서 v_j로 가는 최단경로가 v_k를 거치는 경우

2.2. Floyd의 알고리즘 1
- 문제: 가중치 포함 그래프의 각 정점에서 다른 모든 정점까지의 최단거리를 계산하라
- 입력: 가중치 포함, 방향성 그래프 W와 그 그래프에서의 정점의 수 n
- 출력: 최단거리의 길이 포함된 배열 D

- 시간복잡도
- every case
- 단위연산: for-j 루프 안의 지정문
- 입력크기: 그래프에서의 정점의 수 n
- T(n) = n * n * n = n^3 ∈ Θ(n^3)
- 3차원 배열이 필요하지 않은 이유
- k는 가장 늦게 증가하기 때문에, k번째 node를 경유하는 상태를 계산할 때, k를 중간에 넣기 전 상태이므로 덮어써도 문제 없음
2.3. Floyd의 알고리즘 2
- 출력: 최단경로의 길이가 포함된 배열 D, 다음을 만족하는 배열 P
- P[i][j]: i에서 j까지 가는 최단 경로 중, j 바로 이전에 들른 노드
- vi에서 vj까지 가는 최단경로의 중간에 놓여 있는 정점이 최소한 하나는 있는 경우 -> 놓여 있는 정점 중에서 가장 큰 인덱스
- 최단경로의 중간에 놓여 있는 정점이 없는 경우 -> 0


2.4. 최적의 원칙
- the principle of optimality: 어떤 문제 사례에 대한 최적 해가 그 사례를 분할한 부분 사례에 대한 최적해를 항상 포함하고 있는 경우
- 최적의 원칙이 적용되어야 -> 동적 계획법 사용 가능
- ex) 최단 경로 구하는 문제에서, vk를 vi에서 vj로 가는 최적 경로 상의 정점이라고 하면
- vi에서 vk로 가는 부분경로 & vk에서 vj로 가는 부분경로도 반드시 최적이어야 함
- 최적의 원칙이 적용되지 않는 예: 최장경로(longest path) 문제

- v1에서 v4로의 최장경로는 [v1, v3, v2, v4]
- 그러나 이 경로의 부분경로인 v1에서 v3으로의 최장경로는 [v1, v3]이 아니고, [v1, v2, v3]이다.
3. 연쇄 행렬 곱셈(matrix-chain multiplication)

- i x j 행렬과 j x k 행렬을 곱하기 위해서는 i x j x k번의 곱셈이 필요
- 연쇄적으로 행렬곱셈을 수행할 때, 어떤 행렬곱셈을 먼저 수행하느냐에 따라 필요한 총 곱셈의 횟수가 달라짐
- ex) A1 x A2 X A3
- A1의 크기는 10 X 100, A2의 크기 100 X 5, A3의 크기 5 X 50
- (A1 X A2) X A3 곱셈의 총 횟수 7500 = 5000 + 2500
- (10 * 100 * 5) + (10 * 5 * 50)
- A1 X (A2 X A3) 곱셈의 총 횟수 75000 = 25000 + 50000
- (10 * 100 * 50) + (100 * 5 * 50)
3.1. Brute-force
- 시간복잡도 최소 exponential-time

- Brute Force의 모든 가능한 가지 수 P(n)
- P(n) = C(n - 1): Catalan Number

-> n이 커질수록 기하급수적으로 커지므로 최적화 필요
3.2. Dynamic Programming

- DP 기본 개념
- i ~ k 범위에서의 최소곱셈 & k + 1 ~ j까지의 범위에서의 최소 곱셈
- 그 둘을 곱하는 곱셈의 횟수
- example


- 최적 순서의 구축



-> M[i][j]를 구하기 위해서는 M[i][k]와 M[k+1][j]가 이미 구해져 있어야 함
- 시간복잡도
- every case ∈ Θ(n^3)
4. 최적 이진검색 트리
- Optimal Binary Search Tree: 키를 찾는데 걸리는 평균시간이 최소가 되도록 구축된 트리
- 각 키를 찾을 확률이 주어져 있을 때(모두 같지 않음), 키를 찾는데 걸리는 평균시간이 최소가 되도록 이진트리를 구축하는 문제
- notation
- p_i = key_i의 검색 빈도 -> 얼마나 자주 검색되는지
- c_i = 주어진 트리에서 Key_i를 찾는데 필요한 비교횟수
- 평균검색시간 = Σ(c_i * p_i)

-> DP에서는 매 단계가 1층 기준이므로, c_i = 1 ->> Σp_i

-> 일반화 버전
-> 해당 일반화 식을 이용하여 표를 채운 이후, A[1][n]을 구하면, 해당 값이 최적값
- A 배열: 최적 BST의 평균 검색 시간 -> 최적 BST의 모양은 알 수 없음
- R 배열: A 배열에 최적값을 넣는 순간 k의 값 -> 루트 노드가 k(좌측 서브트리가 1~k-1, 우측 서브트리가 k+1 ~ n)



5. DNA 서열 맞춤(sequence alignment)
- 동족서열(homologous sequence)
- 하나의 종에서 출발하여, 교환, 삽입, 삭제 돌연변이에 의해 서열이 달라짐
- 달라진 두 종에서 추출한 염기서열을 동족서열이라 함
- 이 두 서열이 얼마나 다른지를 확인해서, 종족 간의 거리를 측정
- 두 개의 염기서열을 어떻게 맞춰야 할까?
- DNA 서열 맞춤 문제: 두 개의 서열을 최소비용으로 맞추는 방법을 찾는다.

- opt(i, j): 부분 서열 x[i, ..., 9]와 y[j, ..., 7]의 최적 맞춤 비용
- opt(0, 0): 부분 서열 x[0, ..., 9]와 y[0, ..., 7]의 최적 맞춤 비용 -> 구하려는 최종 비용

- 둘을 맞추는 경우 -> penalty = 0
- 못 맞추는 경우 -> penalty = 1
- y쪽을 틈으로 비워두는 경우 -> +2
- x쪽을 틈으로 비워두는 경우 -> +2

- Divide-and-Conquer 구현


-> 중복호출하는 경우가 너무 많아 비효율적
- Dynamic Programming

1. 틈 행과 틈 열을 채워 넣음

2. 우측 아래부터 대각원소들을 채워 넣음

>> 최적서열맞춤 찾아가는 방법
- [0][0] 선택
- 경로에 넣을 둘째 항목을 찾는다
- [0][1] 검사
- [1][0] 검사
- [1][1] 검사

>> 맞춤된 서열을 구성하는 방법
- 최적배열의 오른쪽 맨 아래 구성에서 시작하여 표시해둔 경로를 따라감
- 배열의 [i][j]칸에 도달하기 위해서 대각선으로 이동할 때 마다,
- i번째 행에 해당하는 문자를 x서열에 넣고
- j번째 행에 해당하는 문자를 y 서열에 넣음
- 배열의 [i][j]칸에 도달하기 위해서 위로 이동할 때 마다,
- i번째 행에 해당하는 문자를 x서열에 넣고
- 틈 문자를 y서열에 넣음
- 배열의 [i][j]칸에 도달하기 위해서 아래로 이동할 때 마다
- 틈 문자를 x서열에 넣고
- j번째 행에 해당하는 문자를 y서열에 넣음

참고자료
최적 이진 탐색 트리 (Optimal Binary Search Tree)
이전 포스팅에서 설명했던 이진 탐색 트리 (BST) 의 활용 예를 보자. 설명할 때는 보통 이해하기 쉽게 노드에 들어있는 데이터를 숫자로 가정하지만, 실제로 쓰일 때는 문자열이라던가 더 다양한
gsmesie692.tistory.com
728x90
반응형