はじめに
今回はナップザック問題を解いてみようという記事になります。
ja.wikipedia.org
ナップザック問題は動的計画法の典型例なので、アルゴリズムの勉強にも良い問題だと思います。
早速みていきましょう。
ナップザック問題とは
「容量 C のナップサックが一つと、n 種類の品物(各々、価値 pi, 容積 ci)が与えられたとき、ナップサックの容量 C を超えない範囲でいくつかの品物をナップサックに詰め、ナップサックに入れた品物の価値の和を最大化するにはどの品物を選べばよいか」
を品物の集合とし,各品目の重みを, 価値を,品物の最大容量をとしたとき,以下のように数式でかけます。
はナップサックへ入れる品物の個数を指します。
問題
今回は,つまり選ぶ・選ばないだけの0/1ナップサック問題を解いてみます。
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DPL_1_B&lang=jp
方針
まずは実装の方針を簡単化するために荷物(品物)の数N
を4こ,ナップザックの容量W
を5とし,各荷物(品物)の価値v
・重さ(容量)w
を以下のように考えます。
- (価値v, 重さw) = (4, 2), (5, 2), (2, 1), (8, 3)
今回の記事は実装が目的なので細かい詳細は省きますが、ざっくりとまとめるとN+1 * W+1
の表を埋めることで答えを導きます。
詳細は以下の記事にめちゃくちゃ丁寧に書かれているので、こちらを一読ください。
典型的な DP (動的計画法) のパターンを整理 Part 1 ~ ナップサック DP 編 ~ - Qiita
dp[i+1][w] := i 番目までの品物の中から重さが w を超えないように選んだときの、価値の総和の最大値
この定義を踏まえながら、以下の手法で表を埋めていきます。
実装
public class KnapsackProblem { private Item[] _items; public KnapsackProblem(IEnumerable<Item> items) { _items = items.ToArray(); } public int[][] Solve(int W) { int N = _items.Length; var dp = new int[N + 1][].Select(t => t = new int[W+1]).ToArray(); // DP初期条件 for (int j = 0; j < W; j++) dp[0][j] = 0; // DPループ for (int i = 0; i < N; i++) { for (int w = 0; w <= W; w++) { if (w >= _items[i].Weight) dp[i + 1][w] = Math.Max(dp[i][w - _items[i].Weight] + _items[i].Value, dp[i][w]); else dp[i + 1][w] = dp[i][w]; } } return dp; } }
使い方
class Program { static void Main(string[] args) { var NW = Console.ReadLine().Split(' ').Select(int.Parse).ToArray(); var N = NW[0]; var W = NW[1]; var items = new Item[N]; for (int i = 0; i < N; i++) { var item = Console.ReadLine().Split(' ').Select(int.Parse).ToArray(); items[i] = new Item(item[0], item[1]); } var problem = new KnapsackProblem(items); var dp = problem.Solve(W); Console.WriteLine(dp.Last().Last()); } }
これを先程の問題のところに提出してみたところ無事通ったので、大丈夫そうです。
さいごに
本当はナップザック問題に特化したものではなくて、もっと抽象的にプログラムを作成したかったのですが結局できませんでした。
もう少し知識が付いたら書き直してみようかなと思ったり。
またナップザック問題のみならず色々なタイプの動的計画法も解いていきたいと思っているので、よければお付き合いください。
ではまた。