再帰を使った場合
容量Wmaxのナップザックに、N個の品物を入れる。
品物iの重さがw[i] 価値がv[i]であるときに、容量Wmaxを超えない、
かつ、価値が最大となるように品物を選択してナップザックに詰める。
その時の価値を求めます。
まずは、再帰を使った全探索の方法です。
下記がサンプルコードになります。
$ cat sample.c
#include <stdio.h>
#include <time.h>
//ナップザックは容量Wmax(引数で設定)
//v[i] w[i]の品物がN個
//(1<= i <= N)
#define N (6)
int w[N] = {1, 4, 7, 2, 7, 2};
int v[N] = {5, 2, 6, 2, 7, 4};
int getNapVal(int i, int Wmax){
int ret1, ret2;
//printf("getNapVal i=%d Wmax=%d\n", i, Wmax);
if(i == N){
return 0;
}else if(w[i] > Wmax){
return getNapVal(i + 1, Wmax);
}else{
ret1 = getNapVal(i + 1, Wmax);
ret2 = getNapVal(i + 1, Wmax - w[i]) + v[i];
return (ret1 > ret2) ? ret1 : ret2;
}
}
int main(){
printf("%d\n", getNapVal(0, 0));
printf("%d\n", getNapVal(0, 3));
printf("%d\n", getNapVal(0, 7));
printf("%d\n", getNapVal(0, 35));
return 0;
}
ポイントは下記の部分ですね。
//品物iをNまで探索したら0を返す
if(i == N){
return 0;
//品物iが容量オーバーでナップザックに詰めることができなければ
}else if(w[i] > Wmax){
//品物iを詰めずに次の品物(i + 1)を検討
return getNapVal(i + 1, Wmax);
//品物iがまだナップザックに詰めることができれば
}else{
//品物iを詰めない場合と
ret1 = getNapVal(i + 1, Wmax);
//品物iを詰めた場合を計算して、
ret2 = getNapVal(i + 1, Wmax - w[i]) + v[i];//残りの容量がw[i]だけ減って、価値がv[i]だけプラスされる
//価値が大きい方をreturnで返す
return (ret1 > ret2) ? ret1 : ret2;
}
下記が実行結果になります。
$ gcc -o sample sample.c $ ./sample 0 9 11 26
動的計画法のメモ化再帰を使った場合
次は、メモ化再帰の方法です。
$ cat sample2.c
#include <stdio.h>
#include <time.h>
//ナップザックは容量Wmax(引数で設定)
//v[i] w[i]の品物がN個
//(1<= i <= N)
#define N (6)
#define WEI_MAX (100)
int memoData[N + 1][WEI_MAX + 1] = {};
int w[N] = {1, 4, 7, 2, 7, 2};
int v[N] = {5, 2, 6, 2, 7, 4};
int getNapVal(int i, int Wmax){
int ret1, ret2;
if(memoData[i][Wmax]){
return memoData[i][Wmax];
}
//printf("getNapVal i=%d Wmax=%d\n", i, Wmax);
if(i == N){
return 0;
}else if(w[i] > Wmax){
return memoData[i][Wmax] = getNapVal(i + 1, Wmax);
}else{
ret1 = getNapVal(i + 1, Wmax);
ret2 = getNapVal(i + 1, Wmax - w[i]) + v[i];
return memoData[i][Wmax] = (ret1 > ret2) ? ret1 : ret2;
}
}
int main(){
printf("%d\n", getNapVal(0, 0));
printf("%d\n", getNapVal(0, 3));
printf("%d\n", getNapVal(0, 7));
printf("%d\n", getNapVal(0, 35));
return 0;
}
ポイントは下記の一度計算していたら、計算はスキップしてメモデータの値を返す箇所です。
if(memoData[i][Wmax]){
return memoData[i][Wmax];
}
計算していなければ、計算してメモデータに結果を格納します。
if(i == N){
return 0;
}else if(w[i] > Wmax){
return memoData[i][Wmax] = getNapVal(i + 1, Wmax);
}else{
ret1 = getNapVal(i + 1, Wmax);
ret2 = getNapVal(i + 1, Wmax - w[i]) + v[i];
return memoData[i][Wmax] = (ret1 > ret2) ? ret1 : ret2;
}
}
下記が実行結果になります。
$ gcc -o sample sample2.c $ ./sample 0 9 11 26
動的計画法で漸化式を使った場合
最後に漸化式を使った場合です。
$ cat sample3.c
#include <stdio.h>
#include <time.h>
//ナップザックは容量Wmax(引数で設定)
//v[i] w[i]の品物がN個
//(1<= i <= N)
#define N (6)
#define WEI_MAX (100)
int dpData[N + 1][WEI_MAX + 1] = {};
int itemW[N] = {1, 4, 7, 2, 7, 2};
int itemV[N] = {5, 2, 6, 2, 7, 4};
int getNapVal(int Wmax){
int ret1, ret2;
int i, w;
for(i = 0; i < N; i++){
dpData[0][i] = 0;
}
for(i = 0; i < N; i++){
//printf("i=%d\n", i);
for(w = 0; w <= Wmax; w++){
//printf("w=%d\n", w);
if(itemW[i] > w){
dpData[i + 1][w] = dpData[i][w];
}else{
ret1 = dpData[i][w - itemW[i]] + itemV[i];
ret2 = dpData[i][w];
dpData[i + 1][w] = (ret1 > ret2 ? ret1 : ret2);
}
}
}
return dpData[N][Wmax];
}
int main(){
printf("%d\n", getNapVal(0));
printf("%d\n", getNapVal(3));
printf("%d\n", getNapVal(7));
printf("%d\n", getNapVal(35));
return 0;
}
ポイントはほぼコード全てになりますが下記ですね。
//品物i=0から順にi=Nまでループ処理する
for(i = 0; i < N; i++){
//printf("i=%d\n", i);
//さらに、品物iに対して、ナップザックの残りの容量を0からWmaxで変化させて
//容量が残りwの場合に価値の最大値を求める
for(w = 0; w <= Wmax; w++){
//printf("w=%d\n", w);
//容量オーバーで、品物iをナップザックに詰めることができなければ
if(itemW[i] > w){
//品物iを詰めず
dpData[i + 1][w] = dpData[i][w];
//品物iが容量wの範囲内で、ナップザックに詰めることができれば
}else{
//品物iを詰めた場合と
ret1 = dpData[i][w - itemW[i]] + itemV[i];
//品物iを詰めない場合を計算して
ret2 = dpData[i][w];
//価値が大きい方を、dpData[i + 1][w]に格納
dpData[i + 1][w] = (ret1 > ret2 ? ret1 : ret2);
}
}
}
下記が実行結果になります。
$ gcc -o sample sample3.c $ ./sample 0 9 11 26
