記事ディレクトリ
動的計画法にはこのようなナップサック問題があり、かなり面倒ですが、一度聞いて数日でまた忘れてしまうので、01ナップサック問題を詳しく説明したブログを書いて、転覆することなく理解できます。 !!
質問:アイテムとサイズのナップザックがあります。各アイテムのサイズを表すます。バックパックに収まる最大合計値はいくらですか?n
m
A
V
ノート:
A[i], V[i], n, m
どちらも整数です- アイテムをスライスすることはできません
- バックパックにパックすることを選択したアイテムの合計サイズは、それを超えることはできません
m
- 各アイテムは一度だけ拾うことができます
- m <= 1000およびlen(A)、len(V)<= 100
思考分析:
この質問に答えるときは、一般の人のことを考えてみてください。バックパックの合計値を最大にしたい場合はn
、各アイテムの量と値を同時に考慮する必要があります。いくつかの選択の後、最適な組み合わせソリューションが得られます。
このn
アイテムの各部分は、入れられるかどうかの可能性があります。
まず、バックパックの容量とバックパックに入れるアイテムのサイズの関係を考慮する必要があります。
- 太くてバックパックよりも大きい商品は、入れられる可能性がなく、紛失してしまいます〜
- アイテムの量がバックパックの量よりも少ない場合は、バックパックに入れる方が価値があるかどうかを検討し、2つのオプションのうちより価値のあるものを選択する必要があります。
上記のアイデアを動的計画法に変換する4つの角度は次のとおりです。
状态定义F(i,j)
:前i个物品放进大小为j的背包中所获得的最大的价值量
。たとえば、F(4、8)は、バックパックのサイズが8のときに最初の4つのアイテムが入れられる状況を考慮して、バックパックの最大値との最良の組み合わせによって生成される値の量を表します。
状态间的转移方程
F(i、j)を定義します。
そのとき
A[i-1] > j
、F(i,j)= F(i-1,j)
;は、i番目のアイテムがナップザックよりも大きくて破棄されることを意味します。このとき、ナップザックの値は、最初のi-1アイテムをサイズのナップザックに入れたときに生成される最大値と同じです。 j。その時
A[i-1] <= j
、F(i,j)= Max(F(i-1,j),F(i-1,j-A[i-1])+V[i-1])
;つまり、i番目のアイテムの体積がバックパックの体積よりも小さい場合、バックパックに入れるかどうかを選択でき、F(i、j)の値は次のようになります。 2つの選択肢の中で最大の価値を生み出すことができます。
バックパックに入れられていない場合、この時点でのバックパックの値は、最初のi-1アイテムをサイズjのバックパックに入れて生成された最大値と同じです。
バックパックに入れる場合は、最初のi-1アイテムに最適なバックパックでi番目のアイテム(A [i-1])のボリュームを空にする必要があります。このボリュームはアイテムを歓迎するために使用されます。iの到着後、i番目のアイテムの値(V [i-1])を加算します。
状态的初始化 F(0,j)= F(i,0)= 0
;アイテムが入っていない場合、またはバックパックのサイズが0の場合、バックパックの値は0です。
返回结果F(n,m)
サイズmのナップザックにn個のアイテムを入れて生成される最大値
分析例:
ここには4つのアイテムがあり、バックパックのサイズは8で、アイテムサイズの配列A [4]={3,5,1,4};アイテム値の配列V[4]={1,3,2,3 };
問題をより鮮明に説明するために、2次元の表を使用して、以前の分析のアイデアを検証します。
横軸はバックパックの容量(j)、縦軸はアイテム番号(i)です。各座標は、最初のi個のアイテムをサイズjのバックパックに入れたときに得られる最大値を表します。
アイテムがない場合、またはバックパックの容量が0の場合、バックパックの値は0であり、これは状態の初期化です。
サイズ1をバックパックに入れるかどうかを決めるとき:
-
バックパックの容量が3(アイテム1のサイズ)未満の場合、アイテム1を置いて廃棄することはできません〜このとき、バックパックの値は0です。
-
バックパックの容量が3以上の場合
-
F(1、3)= max(F(0、3)、F(0、3-3)+ V [0])= F(0、0)+ 1 = 1
-
F(1、4)= max(F(0、4)、F(0、4-3)+ V [0])= F(0、1)+ 1 = 1
…
-
F(1、8)= max(F(0、8)、F(0、8-3)+ V [0])= F(0、5)+ 1 = 1
-
サイズ2をバックパックに入れるかどうかを決めるとき:
- バックパックの容量が5(アイテム2のサイズ)未満の場合、アイテム2を置く可能性はありません。廃棄してください〜。このとき、バックパックの値はF(1)と同じです。 、j)(1 <= j <= 4)
- バックパックの容量が5以上の場合
- F(2、5)= max(F(1、5)、F(1、5-5)+ V [1])= F(1、0)+ 3 = 3
- F(2、6)= max(F(1、6)、F(1、6-5)+ V [1])= F(1、1)+ 3 = 3
- F(2、7)= max(F(1、7)、F(1、7-5)+ V [1])= F(1、2)+ 3 = 3
- F(2、8)= max(F(1、8)、F(1、8-5)+ V [1])= F(1、3)+ 3 = 4
サイズ3をバックパックに入れるかどうかを決めるとき:
-
バックパックの容量が1(アイテム3のサイズ)未満の場合、アイテム3を置く可能性はありません。廃棄してください〜このとき、バックパックの値は0です。
-
バックパックの容量が1以上の場合
-
F(3,1)= max(F(2,1)、F(2,1-1)+ V [2])= F(2,0)+ 2 = 2
-
F(3、2)= max(F(2、2)、F(2、2-1)+ V [2])= F(2、1)+ 2 = 2
…
-
F(3、8)= max(F(2、8)、F(2、8-1)+ V [2])= F(2、7)+ 2 = 5
-
サイズ4をバックパックに入れるかどうかを決めるとき:
-
バックパックの容量が4(4番のサイズ)未満の場合、4番のアイテムを置く可能性はありません。廃棄してください〜。このとき、バックパックの価値はと同じです。 F(3、j)(1 <= j <= 3)
-
バックパックの容量が4以上の場合
-
F(4、4)= max(F(3、4)、F(3、4-4)+ V [3])= F(3、4)= 4
-
F(4,5)= max(F(3,5)、F(3,5-4)+ V [3])= F(3,1)+ 3 = 5
…
-
F(4、8)= max(F(3、8)、F(3、8-4)+ V [3])= F(3、4)+ 3 = 6
-
バックトラック誘導:
表の右下隅からさかのぼると、現在のiアイテムによって生成された最大値は、前のi-1アイテムによって生成された最大値と等しく、i番目のアイテムがナップザックに入れられていないことを示します。 、ナップザックに入れました。
この例では
F(4、8)!= F(3、8)は、アイテム4がバックパックに入れられ、アイテム4のサイズが4であることを意味します。これは、バックパックに他に4つのアイテムが残っていることを意味します。
F(3、4)!= F(2、4)は、アイテム3がバックパックに入れられ、アイテム3のサイズが1であることを意味します。これは、バックパックに他に3つのアイテムが残っていることを意味します。
F(2、3)== F(1、3)は、アイテム2がバックパックに入れられておらず、他に3つのアイテムがバックパックに残っていることを意味します。
F(1、3)!= F(0、3)は、アイテム1がバックパックに入れられ、アイテム1のサイズが3で、バックパックの容量が0であることを意味します。
概要:アイテム1、アイテム3、アイテム4はバックパックに入れられます
コード
public class Solution {
public int backPackII(int m, int[] A, int[] V) {
int n = A.length;//物品的数量
if(n == 0 && m == 0) return 0;//若没有物品或背包容量为0,就直接返回0,背包价值为0
int[][] maxV = new int[n+1][m+1];//创建二维数组来存放价值状态
//状态初始化
//在Java中数组被初始化大小后,每个元素的大小默认为0,因此maxV[i][0]和maxV[0][j]不再初始化也是可以的
for(int i = 0;i <= n;i ++) {
maxV[i][0] = 0;
}
for(int j = 0;j <= m;j ++) {
maxV[0][j] = 0;
}
//状态转移
for(int i = 1;i <= n;i ++) {
for(int j = 1;j <= m;j ++) {
if(A[i-1] <= j) {
maxV[i][j] = Math.max(maxV[i-1][j],maxV[i-1][j-A[i-1]]+V[i-1]);//背包容量大于物品i的情况
}else{
maxV[i][j] = maxV[i-1][j];//背包容量小于物品i的情况
}
}
}
return maxV[n][m];//返回结果
}
}
コードのアップグレード
実際、動的計画問題の状態遷移とは、1ステップを転送して得られる状態を指します。この質問では、i番目のアイテムを配置したときに得られるナップザックの最大値は、 i-1番目の項目。シチュエーションを入力して得られるバックパックの最大値は、シチュエーションを入力して得られるバックパックの最大値に関連しています。バックパックの値は、動的計画問題は、それぞれの小さな問題のステータスを記録し、次に大きな問題を段階的に推測することです。
この場合、サイズm(バックパックの最大容量)の1次元配列を作成するだけで、i-1番目のアイテムを入れたかどうかに関係なくバックパックの値を格納し、その後のバックパックの値を格納できます。 i番目の項目が入れられているかどうか。その1次元配列を直接変更します。
考え方は上記と同じですが、違いは配列が1次元になることです。また、2番目のレイヤーをトラバースする場合は、後ろから前にトラバースする必要があります。前から後ろにトラバースすると、後者の状態が前の状態の値を使用する場合、以前の状態の値が改ざんされており、結果結果は当然間違っています。
これを後ろから前にトラバースする簡単な例:
array [5] = {1,2,3,4,5};
ここで、array [0]を変更せずに、配列内の他のすべての要素が前の要素の2倍になるようにします。つまり、array [i] = array [i-1] * 2(0 <i <5)
方法1:配列a []を再作成して結果を保存します。配列が前から後ろに移動するか、後ろから前に移動するかは関係ありません。配列配列は改ざんされておらず、結果はすべて正しいです。
方法2:スペースを節約するには、配列配列上で直接変更します。前から後ろの場合、array [0] = 1、array [1] = 2、array [2] = 4、array [3 ] = 8 !?バグがあります。6ではないはずです。理由は、次の結果では配列配列の前の値が使用されるためです。前から後ろに移動すると、配列配列の前の値が改ざんされます。 、およびこの状況は後ろから前に発生しません。
この場合、方法1は2次元配列を作成することと同等であり、方法2はこの場合1次元配列を作成することと同等であるため、トラバース方向に注意する必要があります。これは非常に重要です。
public class Solution {
public int backPackII(int m, int[] A, int[] V) {
int n = A.length;//物品的数量
if(n == 0 && m == 0) return 0;//若没有物品或背包容量为0,就直接返回0,背包价值为0
int[] maxV = new int[m+1];//创建一维数组来存放价值状态
for(int j = 0;j <= m;j ++) {
maxV[j] = 0;
}
for(int i = 1;i <= n;i ++) {
for(int j = m;j >=0;j --) {
if(A[i-1] <= j) {
maxV[j] = Math.max(maxV[j],maxV[j-A[i-1]]+V[i-1]);
}
}
}
return maxV[m];
}
}
終了!