점 i에서 점 2를 경유하여 j로 가는 경로의 거리와 Dij^1 중에서 짧은 경로의 거리
단, 점 2를 경유하는 경로의 거리는 Di2^1 + D2j^1
i ≠ 2, j ≠ 2
점 i에서 점 k를 경유하여 j로 가는 경로의 거리와 Dij^(k-1) 중에서 짧은 경로의 거리
점 k를 경유하는 경로의 거리는 Dik^(k-1) + Dkj^(k-1) 이고, i≠k, j≠k
AllPairsShortest
입력: 2차원 배열 D, 단, D[i, j] = 간선 (i, j)의 가중치, 만일 간선 (i, j)
가 없으면 D[i, j] = ∞, 모든 i에 대하여 D[i, i] = 0
출력: 모든 쌍 최단 경로의 거리를 저장한 2차원 배열 D
1. for k = 1 to n
2. for i = 1 to n (i≠k)
3. for j = 1 to n ( j≠k, j≠i)
4. D[i, j] = min{ D[i, k]+D[k, j], D[i, j] }
#include <stdio.h>
#define INF 1000000000 // 매우 큰 값 (무한대 대체)
int main() {
int n = 4; // 노드 수
int graph[4][4] = {
{0, 5, INF, 8},
{7, 0, 9, INF},
{2, INF, 0, 4},
{INF, INF, 3, 0}
};
int dist[4][4];
// 초기화: 그래프의 값을 그대로 dist로 복사
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dist[i][j] = graph[i][j];
}
}
// 플로이드-워샬 수행
for (int k = 0; k < n; k++) { // 중간 노드
for (int i = 0; i < n; i++) { // 출발 노드
for (int j = 0; j < n; j++) { // 도착 노드
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
// 결과 출력
printf("=== 모든 쌍 최단 거리 ===\n");
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][j] == INF)
printf("%7s", "INF");
else
printf("%7d", dist[i][j]);
}
printf("\n");
}
return 0;
}
입력: 연속된 행렬 A1xA2x⋯xAn,
단, A1은 d0xd1, A2는 d1xd2, ⋯, An은 dn-1xdn이다.
출력: 입력의 행렬 곱셈에 필요한 원소의 최소 곱셈 횟수
1. for i = 1 to n
2. C[i, i] = 0
3. for L = 1 to n-1 // L은 부분 문제의 크기를 조절하는 인덱스이다.
4. for i = 1 to n-L
5. j = i + L
6. C[i, j] = ∞
7. for k = i to j-1
8. temp = C[i, k] + C[k+1, j] + di-1dkdj
9. if (temp < C[i, j])
10. C[i, j] = temp
11. return C[1,n]
#include <stdio.h>
#define INF 1000000000 // 무한대 대체값
int minMatrixChain(int d[], int n) {
int C[100][100]; // 최소 곱셈 횟수를 저장할 배열
// 1. 대각선 (자기 자신) 초기화
for (int i = 1; i <= n; i++)
C[i][i] = 0;
// 2. L: 부분 문제의 크기 (chain length)
for (int L = 1; L <= n - 1; L++) {
for (int i = 1; i <= n - L; i++) {
int j = i + L;
C[i][j] = INF;
// 3. 가능한 분할 지점 k를 모두 시도
for (int k = i; k < j; k++) {
int temp = C[i][k] + C[k + 1][j] + d[i - 1] * d[k] * d[j];
if (temp < C[i][j])
C[i][j] = temp;
}
}
}
return C[1][n];
}
int main() {
// 예시: A1(10x20), A2(20x5), A3(5x15)
// 따라서 d = {10, 20, 5, 15}
int d[] = {10, 20, 5, 15};
int n = 3; // 행렬의 개수
int result = minMatrixChain(d, n);
printf("최소 곱셈 횟수: %d\n", result);
return 0;
}
S = s1 s2 s3 s4 ⋯ sm
T = t1 t2 t3 t4 ⋯ tn
➢ E[4, 2]의 해를 알면, t3=‘o’를 삽입하면 E[4, 2] + 1
➢ E[3, 3]의 해를 알면, s4=‘o’를 삭제하면 E[3, 3] + 1
➢ E[3, 2]의 해를 알면, s4=‘o’과 t3=‘o’이 같으므로 E[3, 2] + 0
➢ 0번 행이 0, 1, 2, ⋯, n으로 초기화된 의미: S의 첫 문자를 처리하기 전에, 즉, S가 ε (공 문자열)인 상태에서 T의 문자를 좌에서 우로 하나씩 만들어 가는데 필요한 삽입 연산 횟수를 각각 나타낸다.
➢ 0번 열이 0, 1, 2, ⋯, m으로 초기화된 의미: 스트링 T를 ε로 만들기 위해서, S의 문자를 위에서 아래로 하나씩 없애는데 필요한 삭제 연산 횟수를 각각 나타낸다.
EditDistance
입력: 스트링 S, T, 단, S와 T의 길이는 각각 m과 n이다.
출력: S를 T로 변환하는 편집 거리, E[m, n]
1. for i=0 to m E[i, 0] = i // 0번 열의 초기화
2. for j=0 to n E[0, j] = j // 0번 행의 초기화
3. for i=1 to m
4. for j=1 to n
5. E[i, j] = min{E[i, j-1]+1, E[i-1, j]+1, E[i-1, j-1]+α}
6. return E[m, n]
#include <stdio.h>
#include <string.h>
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define INF 1000000
int EditDistance(char S[], char T[]) {
int m = strlen(S);
int n = strlen(T);
int E[101][101]; // 문자열 길이 제한 100 이하라고 가정
// 1. 초기화
for (int i = 0; i <= m; i++)
E[i][0] = i; // S를 공백으로 만드는 데 필요한 삭제 횟수
for (int j = 0; j <= n; j++)
E[0][j] = j; // 공백에서 T를 만드는 데 필요한 삽입 횟수
// 2. DP 점화식 계산
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
int cost = (S[i - 1] == T[j - 1]) ? 0 : 1; // 문자가 같으면 0, 다르면 1
int insert = E[i][j - 1] + 1; // 삽입
int del = E[i - 1][j] + 1; // 삭제
int replace = E[i - 1][j - 1] + cost; // 대체 or 그대로
E[i][j] = MIN(MIN(insert, del), replace);
}
}
return E[m][n]; // S를 T로 바꾸는 최소 편집 거리
}
int main() {
char S[] = "strong";
char T[] = "stone";
int result = EditDistance(S, T);
printf("'%s' → '%s' 의 최소 편집 거리: %d\n", S, T, result);
return 0;
}
K[i, w] = 물건 1∼i까지만 고려하고, (임시) 배낭의 용량이 w일 때의 최대 가치
단, i = 1, 2, ⋯, n이고, w = 1, 2, 3, ⋯, C
• 문제의 최적해 = K[n, C]
Knapsack
입력: 배낭의 용량 C, n개의 물건과 각 물건 i의 무게 wi와 가치 vi
,
단, i = 1, 2, ⋯, n
출력: K[n, C]
1. for i = 0 to n K[i,0]=0 // 배낭의 용량이 0일 때
2. for w = 0 to C K[0,w]=0 // 물건 0이란 어떤 물건도 고려하지 않을 때
3. for i = 1 to n
4. for w = 1 to C // w는 배낭의 (임시) 용량
5. if ( wi > w ) // 물건 i의 무게가 임시 배낭 용량을 초과하면
6. K[i, w] = K[i-1, w]
7. else // 물건 i를 배낭에 담지 않을 경우와 담을 경우 고려
8. K[i, w] = max{K[i-1, w], K[i-1, w-wi
]+vi
}
9. return K[n, C]
• 최적해 K[4, 10] = 90
#include <stdio.h>
#define MAX_N 100
#define MAX_C 100
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int Knapsack(int n, int C, int w[], int v[]) {
int K[MAX_N + 1][MAX_C + 1];
// 1. 초기화 (0행, 0열)
for (int i = 0; i <= n; i++)
K[i][0] = 0;
for (int wgt = 0; wgt <= C; wgt++)
K[0][wgt] = 0;
// 2. DP 테이블 채우기
for (int i = 1; i <= n; i++) {
for (int wgt = 1; wgt <= C; wgt++) {
if (w[i] > wgt)
K[i][wgt] = K[i - 1][wgt]; // 물건 i를 담지 못함
else
K[i][wgt] = MAX(K[i - 1][wgt],
K[i - 1][wgt - w[i]] + v[i]); // 담는 경우 고려
}
}
// 3. 결과 출력
return K[n][C];
}
int main() {
// 예시 데이터 (교재 예시 기반)
// 물건: 4개
// 무게: 5, 4, 6, 3
// 가치: 10, 40, 30, 50
// 배낭 용량: 10
int n = 4;
int C = 10;
int w[] = {0, 5, 4, 6, 3}; // 0번 인덱스는 사용 안 함
int v[] = {0, 10, 40, 30, 50};
int result = Knapsack(n, C, w, v);
printf("최대 가치: %d\n", result);
return 0;
}
DPCoinChange
입력: 거스름돈 n원, k개의 동전의 액면, d1> d2> ⋯ > dk=1
출력: C[n]
1. for i = 1 to n C[i]=∞
2. C[0]=0
3. for j = 1 to n // j는 1원부터 증가하는 (임시) 거스름돈 액수
4. for i = 1 to k
5. if (di ≤ j) and (C[ j-di
]+1<C[ j])
6. C[ j] = C[ j-di
] + 1
7. return C[n]
#include <stdio.h>
#define INF 1000000
#define MAX_N 1000
#define MAX_K 100
int DPCoinChange(int n, int k, int d[]) {
int C[MAX_N + 1];
// 1. 초기화
for (int i = 1; i <= n; i++)
C[i] = INF;
C[0] = 0;
// 2. 동적 계획법 수행
for (int j = 1; j <= n; j++) { // 거스름돈 1원 ~ n원
for (int i = 0; i < k; i++) { // 모든 동전 액면 고려
if (d[i] <= j && C[j - d[i]] + 1 < C[j]) {
C[j] = C[j - d[i]] + 1;
}
}
}
return C[n];
}
int main() {
// 예시: 동전 종류 {16, 10, 5, 1}, 거스름돈 n = 20
int d[] = {16, 10, 5, 1};
int k = 4;
int n = 20;
int result = DPCoinChange(n, k, d);
printf("거스름돈 %d원에 필요한 최소 동전 수: %d\n", n, result);
return 0;
}
동적 계획(Dynamic Programming) 알고리즘은 최적화 문제를 해결하는 알고리즘으로서 입력 크기가 작은 부분 문제들을 모두 해결한 후에 그 해들을 이용하여 보다 큰 크기의 부분 문제들을 해결하여, 주어진 입력의 문제를 해결하는 알고리즘
DP 알고리즘에는 부분 문제들 사이에 함축적 순서가 존재한다.
모든 쌍 최단 경로(All Pairs Shortest Paths) 문제를 위한 FloydWarshall 알고리즘은 O(n^3) 시간에 해를 찾는다.
경유 가능한 점들을 점 1로부터 시작하여, 점 1과 2, 그 다음엔 점 1,2, 3으로 하나씩 추가하여, 마지막에는 점 1에서 점 n까지의 모든 점을 경유 가능한 점들로 고려하면서, 모든 쌍의 최단 경로의 거리를 계산한다.
연속 행렬 곱셈(Chained Matrix Multiplications) 문제를 위한 O(n^3) DP 알고리즘은 이웃하는 행렬들끼리 곱하는 모든 부분 문제들을 해결하여 최적해를 찾는다.
편집 거리(Edit Distance) 문제를 위한 DP 알고리즘은 E[i, j]를 3개의 부분 문제 E[i, j-1], E[i-1, j], E[i-1, j-1]만을 참조하여 계산한다. 시간 복잡도는 O(mn)이다. 단, m과 n은 두 스트링의 길이이다.
배낭(Knapsack) 문제를 위한 DP 알고리즘은 부분 문제 K[i, w]를 물건 1∼i까지만 고려하고, (임시) 배낭의 용량이 w일 때의 최대 가치로 정의하여, i를 1 ∼ 물건 수인 n까지, w를 1 ∼ 배낭 용량 C까지 변화시키며 해를 찾는다. 시간 복잡도는 O(nC)이다.