diff --git a/contents/algorithm/basic.md b/contents/algorithm/basic.md index cda8c7d..cd7cc8b 100644 --- a/contents/algorithm/basic.md +++ b/contents/algorithm/basic.md @@ -13,6 +13,7 @@ - [분할 정복법 (Divide and Conquer)](#분할-정복법-divide-and-conquer)) - [탐욕 알고리즘 (Greedy)](#탐욕-알고리즘-greedy) - [동적 계획법 (Dynamic Programming)](#동적-계획법-dynamic-programming) + - [0-1 Knapsack](#0-1-knapsack) @@ -537,3 +538,59 @@ if (currentRow - rowNum == abs(colNum - board[rowNum])) > **"큰 문제를 여러개의 하위 문제로 나누어 푼다"** 라는 점에서 > DP는 그 하위의 값이 계속 변하고, DAC는 변하지 않는다는 차이점이 있다. + +### 0-1 Knapsack + +`Knapsack` 알고리즘은 크게 두가지로 나눌 수 있다. + +1. Fractional Knapsack +2. 0-1 Knapsack + +`Fractional Knapsack`의 경우 탐욕 기법을 사용해서 해결할 수 있다. +반면 `0-1 Knapsack`의 경우 탐욕 기법을 통해서는 _정해를 보장할 수 없고_ **동적 프로그래밍**을 사용해서 해결할 수 있다. + +#### 0-1 Knapsack 이란 ? + +1. N개의 보석이 있다. +2. 보석들은 각 보석마다 가격(V)과 무게를 가지고 있다. +3. 도둑이 보석을 훔치려고 한다! 하지만 도둑의 가방에는 정해진 무게(W) 밑으로만 담을 수 있다. +4. 도둑이 배낭 안에 담은 보석의 가격의 합이 최대가 되도록 하라. + +동적 계획법의 기본적인 메커니즘에 의해, 이전 단계에서 구했던 최적해가 다음 단계에서 최적해를 구하는데 포함됨을 기억하며 점화식을 설계해보자. + +```c +KNAPSACK(i, w) : 1~i 번호까지의 보석 중 배낭의 최대 무게가 w일 때의 가질 수 있는 최대의 보석가격합. +``` + +이러한 식을 설계한다고 했을 때, 우리가 원하는 최적해는 `KNAPSACK(N, W)`가 될 것이다. + +배낭 무게를 1부터 W까지 늘려가며, 보석을 넣는다고 해보자. + +이 때 다음 보석을 넣기 전 고려해야할 경우의 수가 3가지 있다. + +1. 다음 보석을 배낭에 담을 수 없다. +2. 다음 보석을 배낭에 담을 수 있다. + + 2-1. 다른 보석을 빼고 현재 보석을 담는다. + + 2-2. 다른 보석과 함께 현재 보석을 담는다. + +보석을 담을 수 없는 조건은 다음과 같다. + +1. 현재 보석의 무게가 가방의 무게보다 무겁다. +2. 다른 보석을 빼고 넣었을 때 이전 단계보다 낮은 가치를 가진다. + +`KNAPSACK(N, W)`는 결국 `KNAPSACK(N-1, w)`와 연관을 갖게 된다. +즉, 이전 보석 번호의 최적해 값을 사용하여 구하게 되는 것이다. + +위의 조건들을 고려하여 점화식을 세워볼 수 있겠다. + +```c + (if w[i] > w) KNAPSACK(i-1, w) +KNAPSACK(i, w) = + (if w[i] <= w) max(KNAPSACK(i-1, w), KNAPSACK(i-1, w-w[i]) + V[i]) +``` + +위의 점화식을 사용해 보석 번호 1부터 N까지, 배낭 무게를 1부터 W까지 반복하며 이차원 배열을 채워나가면 된다. + +이러한 풀이방법을 사용한다면 배낭문제를 `O(N*W)`의 시간복잡도로 해결 할 수 있게 된다.