動的計画法シリーズ「0-1ナップサック問題」

重量を積むことができるWバックパックとNアイテム(各アイテムは異なります)を提供します。各アイテムには、重量と値の2つの属性があります。i重量最初の項目があるwt[i]と値があるval[i]。今、あなたがアイテムをパックするために、このバックパックを使用してみましょう、ロードすることができる最大値は何ですか?

例:

入る:

W = 4, N = 3
wt = [2, 1, 3]
val = [4, 2, 3]

出力:6

説明:最初の2つのアイテムを選択し、バックパックに詰めます。総重量が3以下のW場合、最大値6を取得できます。

このテーマのアイテムは、バッグに梱包されているかどうかにかかわらず、分割することはできません。2つにカットして半分に梱包しているとは言えません。これはおそらく0-1バックパックという用語由来です。

動的計画法ルーチン

  1. 最初のステップは、2つの点を明らかにすることである:「状态」そして「选择」

最初に状態について話しましょう。問題の状況をどのように説明できますか?いくつかのオプションアイテムとバックパックの容量制限を考えると、バックパック問題が発生しますよね?つまり、「バックパックの容量」と「オプションアイテム」の2つの状態があります。

選ぶ以外に、アイテムごとに何を選ぶことができるかを考えるのは簡単です。選択肢は「バックパックに詰める」または「バックパックに入れない」です。

ステータスと選択を理解すると、このフレームワークを適用する限り、動的計画問題は基本的に解決されます。

for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 择优(选择1,选择2...)
  1. 2番目のステップはdp、配列の定義を明確にすることです。

dp配列とは何ですか?実際、これは問題の状況を説明する配列です。つまり、問題の「状態」を明確にしただけなのでdp、配列を使用して状態を表現する必要があります

最初に見つけた「ステータス」を見てください。2つあります。つまり、2次元dp配列が必要で、1つの次元は選択可能なアイテムを表し、1つの次元はバックパックの容量を表します。

dp[i][w]の定義は次のとおりです。前のi項目の場合、現在のバックパックの容量はwであり、この場合にロードできる最大値はdp[i][w]です。

たとえば、dp [3] [5] = 6の場合、その意味は次のとおりです。特定の一連のアイテムについて、最初の3つのアイテムのみが選択されている場合、バックパックの容量が5の場合、6の場合にロードできる最大値。

PS:なぜこのように定義されているのですか?状態転送に便利です、またはこれはルーチンです、ただそれを書き留めてください。動的計画に関する一連の記事を参照することをお勧めします。いくつかの種類の動的計画ルーチンが明確に取り上げられています。

この定義によれば、私たちが望む最終的な答えはですdp[N][W]

基本ケースはdp[0][..] = dp[..][0] = 0、アイテムがない場合、またはバックパックにスペースがない場合、ロードできる最大値が0であることを意味します。

上記のフレームワークを改良します。

int dp[N+1][W+1]
dp[0][..] = 0
dp[..][0] = 0

for i in [1..N]:
    for w in [1..W]:
        dp[i][w] = max(
            把物品 i 装进背包,
            不把物品 i 装进背包
        )
return dp[N][W]
  1. 3番目のステップは「选择」、状態遷移のロジックについて考えることです。

簡単に言うと、上記擬似コードの「バックパックにiアイテムi入れる」と「バックパックにアイテム入れない」をコードに反映させるにはどうすればよいでしょうか。

このステップでは、dp配列の定義とアルゴリズムロジックを組み合わせて分析する必要があります。

dp配列の定義を今繰り返します

dp[i][w]彼は言った:最初のi2つの項目、バックパックの現在の容量w、この場合は最大値をインストールできますdp[i][w]

  • この最初のiアイテムがバックパックにパックされていない場合、明らかに、最大値dp[i][w]はに等しくなりdp[i-1][w]ます。

  • この最初のiアイテムがバックパックにロードさdp[i][w]れる場合、それはに等しいはずdp[i-1][w-wt[i-1]] + val[i-1]です。

まず、i1から始まるのでval合計の値wti-1です。

そしてdp[i-1][w-wt[i-1]]、理解するのは簡単です:i最初のアイテムをインストールしたい場合、この時点で最大値をどのように計算しますか?つまり、i最初のアイテムをロードすることを前提として、バックパックが保持できる最大値はいくつですか?

もちろん、残りの重量w-wt[i-1]制限の下でロードできる最大値に、最初のiアイテムの値を加えたものを探す必要がありますval[i-1]。これは、i最初のアイテムをロードすることを前提としてバックパックをロードできる最大値です。

要約すると、2つのオプションがあります。すべて分析しました。つまり、状態遷移方程式を記述し、コードをさらに改良することができます。

for i in [1..N]:
    for w in [1..W]:
        dp[i][w] = max(
            dp[i-1][w],
            dp[i-1][w - wt[i-1]] + val[i-1]
        )
return dp[N][W]
  1. 最後のステップは、擬似コードをコードに変換し、いくつかの境界条件を処理することです。

w - wt[i-1]配列インデックスが境界を越える原因となる0未満の問題に対処します

ここで配列をトラバースする順序はトラバースです

インデックス 0 1 2 3 4 5
wt
val

完全なコードは次のとおりです。

class Solution {
    
    
  	public int knapSack (int W, int N, int[] wt, int[] val){
    
    
      	int[][] dp = new int[N+1][W+1];
      	for(int i = 1; i <= N; i++){
    
    
          	for(int j = 1; j <= W; j++){
    
    
              	if(j-wt[i-1] < 0){
    
    
                  	dp[i][j] = dp[i-1][j];  // 当前背包容量装不下,只能选择不装入背包
                }else {
    
    
                  	// 装入或者不装入背包,择优
                  	dp[i][j] = Math.max(dp[i-1][j-wt[i-1]]+val[i-1],
                                        dp[i-1][j] );
                }
            }
        }
      	return dp[N][W];
    }
}

時間計算量:O(n ^ 2)

スペースの複雑さ:O(n ^ 2)

元の参照:labuladong

おすすめ

転載: blog.csdn.net/weixin_44471490/article/details/109115709