521 likes | 1.16k Views
동적 계획 (Dynamic Programming). 동적 계획법. 분할정복식 알고리즘 설계법 나누어진 문제가 연관이 없을 경우 Cf) 피보나찌 알고리즘 동적계획법 (Dynamic programming) 상향식 해결법 (bottom-up approach) 분할정복식 방법과 마찬가지로 문제를 나눈 후 , 나누어진 부분들을 먼저 푼다 . 그러나 이미 풀어서 답을 알고 있는 부분의 결과가 다시 필요한 경우에는 반복하여 계산하는 대신에 이미 계산된 결과를 그냥 사용한다. 동적 계획법. 전략
E N D
동적 계획법 • 분할정복식 알고리즘 설계법 • 나누어진 문제가 연관이 없을 경우 • Cf) 피보나찌 알고리즘 • 동적계획법(Dynamic programming) • 상향식 해결법(bottom-up approach) • 분할정복식 방법과 마찬가지로 문제를 나눈 후, • 나누어진 부분들을 먼저 푼다. • 그러나 이미 풀어서 답을 알고 있는 부분의 결과가 다시 필요한 경우에는 반복하여 계산하는 대신에 이미 계산된 결과를 그냥 사용한다.
동적 계획법 • 전략 • 재귀 관계식을 정립 • 상향식 방법 고안 => 프로그래밍 • 작은 사례를 먼저 푼다. • 그 값을 배열에 저장한다. • 나중에 그 값을 사용한다.
(복습) 피보나찌 수열 • 재귀적 방법 int fib (int n) { if (n <= 1) return n; else return fib(n-1) + fib(n-2); } • 동적 계획법 int fib2 (int n) { index i; int f[0..n]; f[0] = 0; if (n > 0) { f[1] = 1; for (i = 2; i <= n; i++) f[i] = f[i-1] + f[i-2];} return f[n]; } Θ(2n) Θ(n)
n n ! = C = n k k k ! ( n - k )! n - 1 n - 1 n + 0 < k < n = k - 1 k k 1 k = 0 or k = n 이항계수
n - 1 n - 1 n + 0 < k < n = k - 1 k k k = 0 or k = n 1 알고리즘: 분할 정복법 int bin(int n,k) { if k=0 or k=n then bin=1; else bin(n,k)= bin(n-1,k-1)+bin(n-1,k); }
시간 복잡도 분석 • 간단하다. 그러나 비효율적이다. • 왜? • 를 구하기 위해, 계산하는 항의 개수: • 증명 • 귀납출발점: n=1; • 귀납 가정: n < N 일때 성립 • 귀납 절차: • 계산하는 항의 개수:
B [ i - 1 , j - 1 ] + B [ i - 1 , j ], 0 < j < i B [ i , j ] = 1 j = 0 or j = i 0 1 2 3 4 j k 1 1 1 1 1 1 1 0 1 2 3 4 i n 1 2 3 4 1 3 6 1 4 1 B[i-1,j-1] B[i-1,j] 알고리즘: 동적 계획법 • 재귀 관계식(recursive property) 정립 • 상향식으로 해결
의사 코드 int bin2(int n,k) { index i,j; int B[0..n,0..k]; for (i=0; i≤n; i++) for (j=0; j ≤ min(i,k); j++) if (j=0 or j=i) B[i,j]=1; else B[i,j] =B[i-1,j-1] +B[i-1,j]; return B[n,k]; }
알고리즘 분석 • 단위 연산: B[i,j] =B[i-1,j-1] +B[i-1,j]; • 입력 크기: n, k • j-루프의 수행 횟수 • 총 수행회수
토의 • 과제: 개선방법 • 배열크기? • 또 다른 특성
최단 경로 찾기 • 하기 전에…
그래프 기초 • 가중치 방향 그래프 • 정점(vertex, node) • 이음선(edge, arc) • 가중치(weight) • 경로(path) • 단순 경로(Simple path) • 순환(cycle) • 순환(cyclic) 그래프 • 비순환(acyclic) 그래프 • 길이(length) • 인접 정점(adjacent vertex)
최단 경로(Shortest Path) • 보기 : 한 도시에서 다른 도시로 직항로가 없는 경우 가장 빨리 갈 수 있는 항로를 찾는 문제 • 문제: 가중치 포함, 방향성 그래프에서 최단경로 찾기 • 최적화문제(optimization problem) • 주어진 문제에 대하여 하나 이상의 많은 해답이 존재할 때, 이 가운데에서 가장 최적인 해답(optimal solution)을 찾아야 하는 문제
최단 경로: 무작정 알고리즘(brute-force algorithm) • 한 정점에서 다른 정점으로의 모든 경로의 길이를 구한 뒤, 그들 중에서 최소길이를 찾는다. • 분석 • 그래프가 n개의 정점을 가지고 있고, 모든 정점들 사이에 이음선이 존재한다고 가정하자. • 그러면 한 정점 i에서 어떤 정점 j로 가는 경로들을 다 모아 보면, 그 경로들 중에서 나머지 모든 정점을 한번씩은 꼭 거쳐서 가는 경로들도 포함되어 있는데, 그 경로들의 수만 우선 계산해 보자. • i에서 출발하여 처음에 도착할 수 있는 정점의 가지 수는 n-2개이고, 그 중에 하나를 선택하면, 그 다음에 도착할 수 있는 정점의 가지 수는 n-3개 이고, 이렇게 계속하여 계산해 보면, 총 경로의 개수는 (n-2)(n-3)…1 = (n-2)!이 된다. • 이 경로의 개수만 보아도 지수보다 훨씬 크므로, 이 알고리즘은 절대적으로 비효율적이다!
동적계획식 설계 전략 - 자료구조 • 그래프의 인접행렬(adjacent matrix)식 표현: W
Floyd 알고리즘 void floyd(int n, const number W[][], number D[][]) { int i, j, k; D = W; for(k=1; k <= n; k++) for(i=1; i <= n; i++) for(j=1; j <= n; j++) D[i][j] = min(D[i][j], D[i][k]+D[k][j]); } • 모든 경우를 고려한 분석 • 단위연산? • 입력크기?
Floyd의 알고리즘 void floyd2(int n, const number W[][], number D[][], index P[][]) { index i, j, k; for(i=1; i <= n; i++) for(j=1; j <= n; j++) P[i][j] = 0; D = W; for(k=1; k<= n; k++) for(i=1; i <= n; i++) for(j=1; j<=n; j++) if (D[i][k] + D[k][j] < D[i][j]) { P[i][j] = k; D[i][j] = D[i][k] + D[k][j]; } }
최단 경로의 출력 • Path(5,3) void path(index q,r) { if (P[q][r] != 0) { path(q,P[q][r]); cout << “ v” << P[q][r]; path(P[q][r],r); } }
동적계획법에 의한 설계 절차 • 문제의 입력에 대해서 최적(optimal)의 해답을 주는 재귀 관계식(recursive property) 설정 • 상향적으로 최적의 해답 계산 • 상향적으로 최적의 해답 구축
최적의 원칙 • 어떤 문제의 입력에 대한 최적 해가 그 입력을 나누어 쪼갠 여러 부분에 대한 최적 해를 항상 포함하고 있으면, 그 문제는 최적의 원칙(the principle of optimality)이 적용된다 라고 한다. • 보기) 최단경로 문제
최적의 원칙이 적용되지 않는 예 • 최장 단순 경로(Longest Path) 문제
연쇄 행렬 곱셈(Matrix-chain Multiplication) • (ixj) 행렬 x (jxk) 행렬: 몇 번 곱셈이 있을까? • ixjxk • 29=1x7+2x2+3x6 이런 계산을 ixk번 수행
연쇄행렬곱셈:무작정 알고리즘 • 알고리즘: 가능한 모든 순서를 구한 다음에 그 중에 가장 최적인 순서를 선택한다. • 시간복잡도: 지수시간 • 왜? • tn: n개의 행렬(A1, A2, …, An)을 곱할 수 있는 가능한 모든 순서의 수 • tn의 부분집합으로서 A1을 맨 마지막으로 곱하는 경우:tn-1 • tn의 부분집합으로서 An을 맨 마지막으로 곱하는 경우: tn-1
연쇄행렬곱셈: 동적계획식 전략 • 최적의 원칙 적용 가능? • 재귀 관계식 구축
연쇄행렬곱셈: 동적 계획식 전략 • 최적의 순서
모든 경우 분석 • 루프 몇 개? • k-루프: (j-1)-i+1=i+diagonal-1-i+1=diagonal • i-루프: n-diagonal
최적의 해 순서 출력 • P[i][j] 이용 void order(index i, index j) { if (i == j) cout << “A” << i; else { k = P[i][j]; cout << “(”; order(i,k); order(k+1,j); cout << “)”; } }
더 나은 방법은? • Yao(1982) • Hu and Shing(1982,1984)
트리의 기본 • 트리(tree) • 루트(root) • 자식 노드(child node) • 루트에서 각 노드까지의 경로가 유일한 구조 • 재귀 구조(recursive structure) • 이진 트리(binary tree) • 깊이(depth)= level
트리의 기본 • 균형 이진 트리(balanced binary tree) • 트리의 모든 노드의 왼쪽 부분트리와 오른쪽 부분트리의 깊이가 최대 하나 차이가 나는 트리
이진 검색 트리(binary search tree) • 순서가 정의되어 있는 항들로 구성된 이진 트리 • 각 노드에는 키가 있다. • 키값은 왼쪽 부분 트리에 있는 모든 키보다 크고, 오른쪽 부분 트리에 있는 모든 키보다 작다. • 최적의 이진 검색 트리 • 키를 찾기 위한 평균 시간 최소화
이진 검색 트리 • 모든 키를 검색할 확률이 같을 경우? • 다를 경우?
이진 검색 트리 • 검색 시간(search time) • 키를 찾기 위한 비교 횟수: depth(key)+1 • pi: keyi를 검색할 확률 • ci: 검색시간 • 평균 검색 시간?
최적 이진 트리 • 무작정 알고리즘의 시간복잡도? • 동적 계획법 이용 가능? • 최적의 원칙이 적용될까? • 부분으로 나누어 보자. • keyi에서 keyj로 구성된 트리에서 최적값 부분해답이다!
동적 계획법: 최적 이진 검색 트리 • keyk가 루트에 있을 때, 평균 검색 시간
모든 경우 분석 • k-루프: min 계산 • j-i+1=i+diagonal-i+1=diagonal+1 • i-루프: n-diagonal
외판원 문제(Traveling Salesperson Problem) • 오일러 순환(Euler cycle): 정점을 한번씩 방문 • 해밀턴 순환(Hamiltonian cycle): 정점을 한번씩 방문하고 돌아옴 • 외판원 문제 • 가장 짧은 해밀턴 순환을 찾음(최적 일주 경로; optimal tour) Length[1,2,3,4,1]=22 Length[1,3,2,4,1]=26 Length[1,3,4,2,1]=21
vi v1 외판원 문제 • 무작정 알고리즘 • (n-1)! • 최적의 원칙 적용 가능? • 동적 계획법으로 풀어보자. • W[i,j]:인접행렬 • V: 모든 정점의 집합 • A: A ⊆ V • D[vi,A]: A에 속한 정점을 한 번씩만 거쳐서 vi 에서 v1로 가는 최단 경로의 길이
외판원 문제 A={v3}D[v2,A] = length[v2,v3,v1] = ∞ A={v3,v4} D[v2,A] = min(length[2,3,4,1],length[2,4,3,1]) = min(20,∞) = 20 Length of an optimal tour = min(W[1,j] + D[vj,V-{v1,vj}]) 2≤j≤n D[vi,A] = min(W[i,j] + D[vj,A-{vj}]) if A ≠ vj∈A D[vi,] = W[i,1]