Haskell で0-1 ナップサック問題をコード化しました。これまでに達成された怠惰さと一般性のレベルについて、私はかなり誇りに思っています。
まず、遅延 2 次元行列を作成して処理するための関数を提供します。
mkList f = map f [0..]
mkTable f = mkList (\i -> mkList (\j -> f i j))
tableIndex table i j = table !! i !! j
次に、特定のナップザックの問題について特定の表を作成します
knapsackTable = mkTable f
where f 0 _ = 0
f _ 0 = 0
f i j | ws!!i > j = leaveI
| otherwise = max takeI leaveI
where takeI = tableIndex knapsackTable (i-1) (j-(ws!!i)) + vs!!i
leaveI = tableIndex knapsackTable (i-1) j
-- weight value pairs; item i has weight ws!!i and value vs!!i
ws = [0,1,2, 5, 6, 7] -- weights
vs = [0,1,7,11,21,31] -- values
最後に、テーブルを表示するためのヘルパー関数をいくつか追加します。
viewTable table maxI maxJ = take (maxI+1) . map (take (maxJ+1)) $ table
printTable table maxI maxJ = mapM_ print $ viewTable table maxI maxJ
ここまではかなり簡単でした。しかし、私はそれをさらに一歩進めたいと思っています。
テーブルのデータ構造を改善したい。理想的には、
ボックス化されていない (不変)[編集] 気にしないで- 怠惰
- 無制限
O(1)
構築する時間O(1)
特定のエントリを検索するための時間の複雑さ
(より現実的には、最悪のO(log n)
場合、ni*j
は行 i、列 j のエントリを検索するためのもの)
ソリューションがこれらの理想を満たしている理由/方法を説明できればボーナス ポイントです。
knapsackTable
をさらに一般化し、それが効率的であることを証明できれば、ボーナス ポイントも得られます。
データ構造を改善するには、次の目標を満たすようにしてください。
- 最大重みが 10 であるソリューションを求める場合 (私の現在のコードでは
indexTable knapsackTable 5 10
、5 は項目 1 ~ 5 を含むことを意味します)、必要最小限の作業のみを実行する必要があります。理想的には、これはO(i*j)
、テーブルの各行の背骨を必要な列の長さに強制する作業がないことを意味します。DP がテーブル全体を評価することを意味すると考える場合、これは「真の」DP ではないと言えます。 - テーブル全体を印刷するように要求した場合 (のようなもの
printTable knapsackTable 5 10
)、各エントリの値は一度だけ計算する必要があります。特定のセルの値は、他のセルの値に依存する必要があります (DP スタイル: 考え方として、同じ部分問題を 2 回再計算しないでください)。
アイデア:
- Data.Array境界 :(
- UArray厳密 :(
- メモ化技術(HaskellのDPに関するSOの質問)これはうまくいくかもしれません
私の述べた理想にある程度の妥協をする回答は、有益である限り、(とにかく私によって)支持されます。妥協が最も少ない答えは、おそらく「受け入れられる」ものになるでしょう。