フラクショナルナップサック問題:例を使用した欲張りアルゴリズム

⚡ スマートサマリー

分数ナップサック問題は、価値と重量の比率に基づいて荷物を並べ替え、その順序でアイテムを取り出す貪欲アルゴリズムを使用し、残りの容量をアイテムの一部で埋めることで、最適な解を保証します。

  • 💡 貪欲な戦略: 全体的な問題に対するグローバルな最適解に到達することを期待して、各ステップで局所的な最適解の選択が行われる。
  • <XNUMXxEXNUMX><XNUMXxEXNUMX><XNUMXxXNUMXA><XNUMXxXNUMX><XNUMXxXNUMXA>️️ 価値/重量比: 選択を開始する前に、パッケージは単価 V[i] / W[i] の降順にソートされます。
  • 📦 分数法則: 次のパッケージの一部を切り取って残りの容量を埋めることで、部分的なバリエーションに対して最適なソリューションが保証されます。
  • 豪華<XNUMXxXNUMXF><XNUMXxXNUMXF><XNUMXxBXNUMX><XNUMXxBXNUMX>️ 複雑: クイックソートまたはマージソートではO(n log n)となり、選択ループよりもソートステップが支配的となる。
  • 🚫 制限: 同じ貪欲法は、アイテムを分割できない0/1ナップサック問題では機能しないため、代わりに動的計画法が使用されます。
  • 🚀 用途: 貨物積載、ポートフォリオ配分、クラウド帯域幅共有、AIリソーススケジューリングはすべて、フラクショナルナップサック法に依存している。

分数ナップサック問題に対する貪欲アルゴリズム

貪欲な戦略とは何ですか?

欲張りアルゴリズム 各段階で最適な局所的選択肢を選び、局所最適解の連鎖によって全体最適解が得られることを期待する。動的計画法と同様に最適化問題を対象としているが、過去の決定を再検討するために過去を振り返ることはない。

貪欲アルゴリズムは通常、記述が簡単で、高速(多くの場合、線形時間または二次時間)、デバッグが容易で、メモリ使用量も少ないという利点があります。ただし、結果が常に最適とは限らないため、この手法は貪欲法が安全に機能することが証明されている問題にのみ有効です。

貪欲法は、組み合わせ最適化問題を、一度に1つの構成要素Aiずつ構築することで解決します。各ステップで、現在の制約の下で最適なAiを選択し、問題をより小さな部分問題に縮小します。

貪欲法が正しいためには、以下の2つの性質が満たされなければならない。

  1. 貪欲選択特性: 各段階における局所最適解は、最終的に全体最適解へと導く。選択は過去の決定に依存するが、将来の決定には依存しない。
  2. 最適な部分構造: 問題全体の最適解は、その部分問題の最適解を含んでいる。

貪欲アルゴリズムには XNUMX つのコンポーネントがあります。

  1. 解決策を構築するための候補集合。
  2. 最適な次候補を選択する選択機能。
  3. 候補が現在の部分解を拡張できるかどうかをチェックする実現可能性関数。
  4. 完全な解または部分的な解を評価する目的関数。
  5. 解が完成したことを知らせる評価関数。

貪欲な者のアイデア

貪欲な人は、パッケージを値だけでソートします。

  • パッケージを価格の低い順に並べ替えてください。
  • ソートされたリストを順に見ていき、残りの容量に余裕があれば、各荷物をナップサックに追加する。

このルールは必ずしも最適な答えを与えるとは限りません。反例:

  • パラメータ: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] を計算します。
  • 単価の低い順にパッケージを並べ替えてください。
  • ソートされたリストを順に処理し、残りの容量に余裕があれば各パッケージを追加します。

貪欲な3人組が単価順に並べ替える

貪欲法 単位コスト V[i] / W[i] による 3 つのソート

アイディア: 各パッケージの価値対重量比 V[i] / W[i] を計算し、降順に並べ替え、ナップサックがいっぱいになるまで利用可能な最大の比率から順に選択します。

真実のために 分数の 変形ルールでは、次のパッケージが丸ごと収まらない場合は、残りの容量をちょうど満たす分数を取る。この追加ルールこそが、分数ナップサック問題においてグリーディ3が最適であることが証明されている理由である。

欲張りな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

関数knapsackGreProc() Java

コードの説明:

  1. すべての入力をラップして KnapsackPackage そのため、ソートキー(V/W比)は事前に計算されます。
  2. コストの高い順に並べ替えてください。
  3. 荷物が収まる場合は、それぞれのパッケージを丸ごと持ち帰ってください。
  4. 残りの容量を埋めるために、次のパッケージの一部を取り分けてください。
  5. 残容量がゼロになったらすぐに停止してください。

修正メモ: オリジナル 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

関数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()

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のごく一部を使用することができます。

よくあるご質問

分数ナップサック問題とは、容量Mのナップサックに、分割可能な品物を詰める問題です。各品物には重さと価値があり、目標は容量を守りながら合計価値を最大化することです。

重量対価値比率で並べ替え、比率の高いものから順に取り出す方法は、比率の低いものに切り替えると容量単位あたりの合計価値が低下するため、明らかに最適です。分数を用いることで、最後のアイテムが残りのスペースにぴったり収まるようになります。

分数ナップサック問題では、任意のアイテムの一部を取り出すことができ、貪欲な価値/重量ソートによって解決されます。 0/1 ナップサック 全体項目が必要であり、最適な解を求めるには動的計画法が必要となる。

値と重みの比率によるソートが実行時間の大部分を占めます。クイックソートやマージソートでは、アルゴリズムの実行時間はO(n log n)です。選択ソートやバブルソートでは、実行時間はO(nの2乗)に増加します。貪欲な選択ループ自体の実行時間はO(n)です。

分数を用いない場合、貪欲な選択では、より賢明な交換によって埋められるはずの未使用の容量が残ってしまう可能性があります。典型的なケース(W = 7、6、4、V = 9、6、4、M = 10)では値9が選択されますが、最適な0/1の回答では10に達します。

ばら積み貨物の積載、ポートフォリオの割り当て、クラウド帯域幅の共有、CPUタイムスライススケジューリング、分割可能なワークロード間でのAIリソースの割り当てなど、重量に基づいて項目を分割できるあらゆる状況が対象となります。

強化学習エージェントは、GPUやメモリの制限内でクラウドタスクを処理し、機械学習モデルは良好な分岐限定法の順序を予測します。分数バリアントでは、貪欲法が最適であるため、AIは主に0/1のケースを対象とします。

はい。GitHub Copilot は、値/重みソート、貪欲ループ、および部分充填ステップをスキャフォールディングします。 Java, Pythonまたは C# で記述し、アルゴリズムが古典的な入力セットに対して既知の最適解に到達することを検証する単体テストを生成します。