フラクショナルナップサック問題:例を使用した欲張りアルゴリズム
⚡ スマートサマリー
分数ナップサック問題は、価値と重量の比率に基づいて荷物を並べ替え、その順序でアイテムを取り出す貪欲アルゴリズムを使用し、残りの容量をアイテムの一部で埋めることで、最適な解を保証します。
貪欲な戦略とは何ですか?
欲張りアルゴリズム 各段階で最適な局所的選択肢を選び、局所最適解の連鎖によって全体最適解が得られることを期待する。動的計画法と同様に最適化問題を対象としているが、過去の決定を再検討するために過去を振り返ることはない。
貪欲アルゴリズムは通常、記述が簡単で、高速(多くの場合、線形時間または二次時間)、デバッグが容易で、メモリ使用量も少ないという利点があります。ただし、結果が常に最適とは限らないため、この手法は貪欲法が安全に機能することが証明されている問題にのみ有効です。
貪欲法は、組み合わせ最適化問題を、一度に1つの構成要素Aiずつ構築することで解決します。各ステップで、現在の制約の下で最適なAiを選択し、問題をより小さな部分問題に縮小します。
貪欲法が正しいためには、以下の2つの性質が満たされなければならない。
- 貪欲選択特性: 各段階における局所最適解は、最終的に全体最適解へと導く。選択は過去の決定に依存するが、将来の決定には依存しない。
- 最適な部分構造: 問題全体の最適解は、その部分問題の最適解を含んでいる。
貪欲アルゴリズムには XNUMX つのコンポーネントがあります。
- 解決策を構築するための候補集合。
- 最適な次候補を選択する選択機能。
- 候補が現在の部分解を拡張できるかどうかをチェックする実現可能性関数。
- 完全な解または部分的な解を評価する目的関数。
- 解が完成したことを知らせる評価関数。
貪欲な者のアイデア
貪欲な人は、パッケージを値だけでソートします。
- パッケージを価格の低い順に並べ替えてください。
- ソートされたリストを順に見ていき、残りの容量に余裕があれば、各荷物をナップサックに追加する。
このルールは必ずしも最適な答えを与えるとは限りません。反例:
- パラメータ:n = 3、M = 19。
- パッケージ: {i = 1; W = 14; V = 20}、{i = 2; W = 6; V = 16}、{i = 3; W = 10; V = 8} — 値が高いが重みも高い。
- 欲張りな人は合計価値20のパッケージ1を選びますが、最適な選択肢(パッケージ2、パッケージ3)は24に達します。
貪欲な二人のアイデア
グリーディ・ツーは、重量だけで荷物を分類します。
- 荷物を重量の降順(増加順ではない)に並べ替えてください。
- ソートされたリストを順に見ていき、残りの容量に余裕があれば、各荷物をナップサックに追加する。
このルールも最適とは言えません。反例:
- パラメータ:n = 3、M = 11。
- パッケージ: {i = 1; W = 5; V = 10}、{i = 2; W = 6; V = 16}、{i = 3; W = 10; V = 28} — 軽量だが価値は低い。
- 貪欲な選択では、合計価値が26の2つの選択肢(パッケージ1、パッケージ2)がありますが、最適な選択肢(パッケージ3)は28に達します。
貪欲なXNUMX人のアイデア
グリーディ3は、価値と重みを単一のランキングキーに統合することで、両方の欠点を解消します。これは、分数ナップサック問題における標準的な手法です。
- 各パッケージの単位コスト V[i] / W[i] を計算します。
- 単価の低い順にパッケージを並べ替えてください。
- ソートされたリストを順に処理し、残りの容量に余裕があれば各パッケージを追加します。
貪欲法 単位コスト V[i] / W[i] による 3 つのソート
アイディア: 各パッケージの価値対重量比 V[i] / W[i] を計算し、降順に並べ替え、ナップサックがいっぱいになるまで利用可能な最大の比率から順に選択します。
真実のために 分数の 変形ルールでは、次のパッケージが丸ごと収まらない場合は、残りの容量をちょうど満たす分数を取る。この追加ルールこそが、分数ナップサック問題においてグリーディ3が最適であることが証明されている理由である。
アルゴリズムの手順
0/1分岐限定法の場合、ソートされた単位コストリストが探索木を駆動します。
- ステップ1: ルートノードは空のナップサックを表します。TotalValue = 0。UpperBound = M × 最大単位コスト。
- ステップ2: 最大比率のパッケージが収まる数だけルートを分岐させる。各子ノードについて、TotalValue、残りの容量M、およびUpperBoundを再計算する。
- ステップ3: 最も上限値が大きい子ノードから先に展開することで、強力な解を迅速に見つけることを目指します。
- ステップ4: UpperBoundが現在の最良の完全解よりも優れていないノードはすべて削除します。
- ステップ5: すべてのノードが展開または剪定された場合、現在の最良の完全解が最適解となる。
純粋な分数ナップサック貪欲アルゴリズムの擬似コード:
Fractional Knapsack (Array W, Array V, int M) 1. for i <- 1 to size(V) 2. cost[i] <- V[i] / W[i] 3. Sort-Descending(cost) 4. total <- 0 5. i <- 1 6. while (i <= size(V) and M > 0) 7. if W[i] <= M 8. M <- M - W[i] 9. total <- total + V[i] 10. i <- i + 1 11. else 12. total <- total + V[i] * (M / W[i]) 13. M <- 0
アルゴリズムの複雑さ:
- 単純なソート(選択またはバブル)を使用する場合:O(n)2).
- クイックソートまたはマージソートを使用する場合:O(n log n)、ソートステップが支配的。
Java Code 貪欲な三人組のために
定義 KnapsackPackage 重み、値、および派生コスト(ソートに使用されるV/W比率)を含むクラス:
public class KnapsackPackage { private double weight; private double value; private Double cost; public KnapsackPackage(double weight, double value) { super(); this.weight = weight; this.value = value; this.cost = Double.valueOf(value / weight); } public double getWeight() { return weight; } public double getValue() { return value; } public Double getCost() { return cost; } }
次に、Greedy Threeを実装する関数を作成します。
public void knapsackGreProc(int W[], int V[], int M, int n) { KnapsackPackage[] packs = new KnapsackPackage[n]; for (int i = 0; i < n; i++) { packs[i] = new KnapsackPackage(W[i], V[i]); } Arrays.sort(packs, new Comparator<KnapsackPackage>() { @Override public int compare(KnapsackPackage a, KnapsackPackage b) { return b.getCost().compareTo(a.getCost()); } }); double remain = M; double result = 0d; for (int i = 0; i < n && remain > 0; i++) { if (packs[i].getWeight() <= remain) { remain -= packs[i].getWeight(); result += packs[i].getValue(); System.out.println("Pack " + i + " - Weight " + packs[i].getWeight() + " - Value " + packs[i].getValue()); } else { double fraction = remain / packs[i].getWeight(); result += packs[i].getValue() * fraction; System.out.println("Pack " + i + " - Fraction " + fraction + " - Value " + packs[i].getValue() * fraction); remain = 0; } } System.out.println("Max Value:\t" + result); }
関数knapsackGreProc() Java
コードの説明:
- すべての入力をラップして
KnapsackPackageそのため、ソートキー(V/W比)は事前に計算されます。 - コストの高い順に並べ替えてください。
- 荷物が収まる場合は、それぞれのパッケージを丸ごと持ち帰ってください。
- 残りの容量を埋めるために、次のパッケージの一部を取り分けてください。
- 残容量がゼロになったらすぐに停止してください。
修正メモ: オリジナル Java ループアドバンス i パッケージが収まらなかった場合にのみ、同じパッケージが繰り返し取られるという問題が発生しました。上記のバージョンでは、反復ごとにパッケージを1つずつ進め、部分充填ステップを追加することで、真の分数ナップサックルールに一致させています。
Java アルゴリズムを具体的な例題で実行するドライバ:
public void run() { int W[] = new int[]{15, 10, 2, 4}; int V[] = new int[]{30, 25, 2, 6}; int M = 37; int n = V.length; knapsackGreProc(W, V, M, n); }
Python3 Code 貪欲な三人組のために
まず定義します KnapsackPackage クラス。 ザ・ __lt__ この方法を使うと、コストで直接ソートできるようになります。
class KnapsackPackage(object): """Knapsack Package Data Class""" def __init__(self, weight, value): self.weight = weight self.value = value self.cost = value / weight def __lt__(self, other): return self.cost < other.cost
次に、部分ナップサックルーチンを実装します。
class FractionalKnapsack(object): def knapsackGreProc(self, W, V, M, n): packs = [KnapsackPackage(W[i], V[i]) for i in range(n)] packs.sort(reverse=True) remain = M result = 0 for i in range(n): if remain == 0: break if packs[i].weight <= remain: remain -= packs[i].weight result += packs[i].value print("Pack", i, "- Weight", packs[i].weight, "- Value", packs[i].value) else: fraction = remain / packs[i].weight result += packs[i].value * fraction print("Pack", i, "- Fraction", fraction, "- Value", packs[i].value * fraction) remain = 0 print("Max Value:", result)
関数knapsackGreProc() Python
修正メモ: オリジナル Python クラスは空を定義しました __init__ 体がない、 IndentationError上記のバージョンでは、空のコンストラクタは不要なので削除されています。
最初の例でアルゴリズムを実行するドライバ:
if __name__ == "__main__": W = [15, 10, 2, 4] V = [30, 25, 2, 6] M = 37 n = 4 proc = FractionalKnapsack() proc.knapsackGreProc(W, V, M, n)
C# Code 貪欲な三人組のために
定義 KnapsackPackage クラス:
using System; namespace KnapsackProblem { public class KnapsackPackage { private double weight; private double value; private double cost; public KnapsackPackage(double weight, double value) { this.weight = weight; this.value = value; this.cost = value / weight; } public double Weight { get { return weight; } } public double Value { get { return value; } } public double Cost { get { return cost; } } } }
分数フィルステップを用いたグリーディ3を実装する:
public void KnapsackGreProc(int[] W, int[] V, int M, int n) { KnapsackPackage[] packs = new KnapsackPackage[n]; for (int k = 0; k < n; k++) packs[k] = new KnapsackPackage(W[k], V[k]); Array.Sort<KnapsackPackage>(packs, (a, b) => b.Cost.CompareTo(a.Cost)); double remain = M; double result = 0d; for (int i = 0; i < n && remain > 0; i++) { if (packs[i].Weight <= remain) { remain -= packs[i].Weight; result += packs[i].Value; Console.WriteLine("Pack " + i + " - Weight " + packs[i].Weight + " - Value " + packs[i].Value); } else { double fraction = remain / packs[i].Weight; result += packs[i].Value * fraction; Console.WriteLine("Pack " + i + " - Fraction " + fraction + " - Value " + packs[i].Value * fraction); remain = 0; } } Console.WriteLine("Max Value:\t" + result); }
C# の関数 KnapsackGreProc()
反例:0/1ナップサックでのグリーディ3
Greedy ThreeはFractionalバリアントでは最適ですが、0/1 Knapsack(アイテムを分割できない)ではそれを上回ることができます。反例:
- パラメータ:n = 3、M = 10。
- パッケージ: {i = 1; W = 7; V = 9; cost = 9/7}、{i = 2; W = 6; V = 6; cost = 1}、{i = 3; W = 4; V = 4; cost = 1}。
- 貪欲な3人は合計値9のパッケージ1を選びますが、最適な0/1の選択(パッケージ2、パッケージ3)は10に達します。
教訓:分数が許される場合にのみグリーディ3を使用してください。0/1バリアントの場合は、 動的計画法 を代わりにお使いください。
分数ナップサックの応用
- 液体、粉末、またはばら積み貨物を重量別に分割して積載できる貨物積載方法。
- 部分的な資金提供を受け入れる投資オプション間でのポートフォリオ配分。
- クラウド帯域幅共有では、フローがリンクのごく一部を消費する可能性がある。
- 分割可能なワークロードを持つ共有タイムスライスモデルにおけるCPUスケジューリング。
- AIリソースの割り当てにおいて、トレーニングジョブはGPUのごく一部を使用することができます。






