制約を最小にする最適解を見つけることは?

この問題をSlinger-Birdの問題と呼ぶことにしましょう(実際にはSlingerはサーバーに似ていますが、リクエストには鳥ですが、神経を壊して考えていましたので、別の見方をしたいと思って変更しました)。

  • Sストーン・スローダ(スリンガ)とB鳥があります。
  • スリンジャーが互いの範囲内にない
  • 一度スリングすると、スリンジャーの目の前ですべての鳥を殺すことができ、ワンショットと1時間単位を消費します。

私は、鳥の特定の到着パターンが与えられたときに鳥を殺すのにかかる時間とショット数を最小限に抑える最適な解決策を見つけようとしています。 3つのスリンガと4つの鳥の絶対数を例に挙げてみましょう。

        Time        1            2            3           4             5
Slinger
S1                B1, B2     B1, B2, B3       B4
S2                               B1         B1, B2      B3,B4     
S3                  B1         B3, B4                 B1,B2,B3,B4

私のデータは次のようになります:

>> print t
[
  {
    1: {S1: [B1, B2], S2: [], S3: [B1]}, 
    2: {S1: [B1, B2, B3], S2: [B1], S3: [B3, B4]},
    3: {S1: [B4], S2: [B1,B2], S3: []},
    4: {S1: [], S2: [B3, B4], S3: [B1, B2, B3, B4]}
  }
]

私が考えることができるいくつかの解決策があります(Sxはt = kで、スリンガーSxは時間kでショットを取ることを意味します)。

  1. S1 at t=1, S1 at t=2, S1 at t=3 <- Cost: 3 shots + 3 time units = 6
  2. S1 at t=2, S1 at t=3 <- Cost: 2 shots + 3 time units = 5
  3. S1 at t=1, S3 at t=2 <- Cost: 2 shots + 2 time units = 4
  4. S3 at t=4 <- Cost: 1 shot + 4 time units = 5

私にとっては解決策 3 がこの中で最適なものであるようです。もちろん、私は手でこれをやったので(私は何かを逃した可能性があります)しかし、私はこれを行うスケーラブルな方法を考えることができません。また、私はコーナーケースがあることを心配しています。なぜなら、1人のシューティングゲームの決定が他のプレイヤーの決定を変えるかもしれないからです。しかし、私は世界的な見解を持っているので、問題ではないかもしれません。

Pythonでこの問題を解決するためのすばらしい、良い方法は何ですか?私はこれを行うためのアルゴリズムを単なるままにするために、良いデータ構造を思いついています。ダイナミックプログラミングを使うことを考えているのは、これは状態空間探査を伴うように思われるが、進める方法についてちょっと混乱しているからだ。助言がありますか?

7
@ mhum:それについてもっと考えてみると、この問題は、時間の概念を取り除き、各セットにコスト関数を付けることによって、実際に最小セットカバーにエンコードできると考えています。もちろん、スリンジャーを問題にエンコードする方法はわかりません。このアプローチについて何か提案がありますか?
追加された 著者 Legend,
参考までに:期間が1つの場合、問題は最小セットカバーに相当します。
追加された 著者 mhum,

3 答え

これは最適な割り当て問題ではありません。スリンガがすべての鳥を見ることができるからです。

あなたは2次元の目的関数を持っているので、ショットと時間の間には多くのトレードオフがあります。特定の時間制限のショットの最小数を決定することは、セットカバーの問題(mhumが示唆するように)とまったく同じです。セットカバーの問題はNP困難で近似はしにくいが、実際には、線形計画式の二分法を用いた分岐と結合は最適を見つけるのにかなり効果的である。

4
追加された
+1ヒントありがとうございます。私はLPのエキスパートではありませんが、私は問題をエンコードする際に挑戦します。私はこれをトップにコメントとして書きましたが、私は時間の概念を取り除き、代わりに各セルにコスト関数を付けることを考えています。しかし、私が言及したように、私はそれをどのように定式化するかについて完全にはわかりません。
追加された 著者 Legend,

私は、スリンガや鳥のためのビットマップを使用することをお勧めします。

S1 = B1 = 1, S2 = B2 = 2, S3 = B3 = 4, B4 = 8

次に、入力データは次のように書くことができます。

bird_data = [[3, 0, 1], [7, 1, 12], [8, 3, 0], [0, 12, 15]]

コスト関数は次のように書くことができます:

def cost(shots):
    hit = units = 0
    for show, shot in zip(bird_data, shots):
        units += 1
        for n, birds in enumerate(show):
            if shot & 1:
                units += 1
                hit |= birds
                if hit == 15: # all are hit
                    return units
            shot >>= 1
    return 99 # penalty when not all are hit

今度は、コスト関数の最小値を計算することによって最適なショットを見つけるのは簡単です:

from itertools import product

shot_sequences = product(*([range(7)]*len(bird_data)))

print min((cost(shots), shots) for shots in shot_sequences)

これが印刷されます

(4, (0, 5, 0, 0))

S1とS3(5 = 1 + 4)がt = 2で発火するとき、最適値は4単位です。もちろん、S1がt = 1で、S3がt = 2の場合、どちらも同じコストがかかります。

しかし、このアルゴリズムはすべての可能なショットシーケンスを実行しているブルートフォースを使用しているので、あなたの例のように、データセットが非常に小さいときは速く実行可能です。

1
追加された
+1ありがとうございます。私はエンコーディングを理解しているとは思っていませんが、別のショットを取ります。私の実際の問題では、時間の長さは約10000であると思うので、これは実現可能ではないかもしれません。
追加された 著者 Legend,

私はあなたがアルゴリズムを開始しているときにあなたがこの例で与えるすべての数字を知っていると仮定しているし、t1を終えた後t2を得ない

私はまた、一度に2人のスリンガーが発砲できると考えていますが、それはあまり重要ではありません。

最初の選択肢では、各セルにamountOfBirdsInCell-timeの値を割り当てることができます。

これにより、値1の2つのセルが得られ、S1t1、S1t2となり、残りのセルはより低くなります。

あなたのスコアの最後のセルの時間だけがカウントされるので、最も早いものを選ぶと、次のラウンドの時間が削除され、最も貴重な時間になります。それが最初の選択です。

さあ、最初のピックで殺された鳥をすべての細胞から取り除いてください。

残りのセルについては、値決定プロセスを繰り返します。あなたの例では、セルS3t2は最高の結果を与え、0になります。

このプロセスを繰り返すと、最も早い時期に最も貴重な細胞が得られます。

あなたの例でカバーしていない重要なビット:最初に最も価値の高いピックがt2にある場合、次に最も価値の高いピックはt1またはt2にあるかもしれないので、それらを考慮する必要があります。しかし、すでにt2が確認されているので、その価値を考慮に入れてはなりません。

私はPythonで書かなかった、私はアルゴリズムタグのためにここにいるので、ここにいくつかのjava/cのような擬似コードがあります:

highestCellTime = 0;
while(birdsRemain)
{
    bestCell;

    for(every cell that has not been picked yet)
    {
        currentCellValue = amountOfBirds;
        if(currentCellTime > highestCellTime)
        {
            currentCellValue = currentCellValue - currentCellTime;
        }

        if(currentCellValue < bestCellValue)
        {
            bestCell = thisCell;
        }
        else if(currentCellValue == bestCellValue && currentCellTime < bestCellTime)
        {
            bestCell = thisCell;
        }
    }
    addCellToPicks(bestCell);
    removeBirdsFromOtherCells(bestCellBirds);
}

私が何かを忘れていなければ、あなたの選択コレクションに最適な細胞の組み合わせができました。

私はこのコードがPythonのプログラマーには意味があることを願っています。もし誰かがそれを翻訳できるなら、してください!そして、テキストのこのビットと以前のjava/c-pseudocodeの記述を削除してください。

EDIT by OP: First version and does not end up with the best cells. I am guessing it must be a bug in my code but nevertheless I am posting here.

import math

cellsNotPicked = range(0,12)
cellToBird = {
    0: [1, 2],
    1: [],
    2: [1],
    3: [1,2,3],
    4: [1],
    5: [3,4],
    6: [4],
    7: [1,2],
    8: [],
    9: [],
    10: [3,4],
    11: [1,2,3,4]
}

picks = []

def getCellValue(cell):
    return len(cellToBird[cell])

def getCellTime(cell):
    return int(math.floor(cell/3)) + 1

birdsRemain = 4

while(birdsRemain > 0):

    bestCell = 0;

    for thisCell in cellsNotPicked:

        currentCellValue = getCellValue(thisCell);

        currentCellTime = getCellTime(thisCell)
        highestCellTime = getCellTime(bestCell)

        if(currentCellTime > highestCellTime):
            currentCellValue = currentCellValue - currentCellTime;

        if(currentCellValue < getCellValue(bestCell)):
            bestCell = thisCell
        elif (currentCellValue == getCellValue(bestCell)) and (currentCellTime < getCellTime(bestCell)):
            bestCell = thisCell

    picks.append(bestCell)
    cellsNotPicked.remove(bestCell)

    birdsToRemove = cellToBird[bestCell]

    for key in cellToBird:
        for bird in birdsToRemove:
            try:
                cellToBird[key].remove(bird)
                birdsRemain -= 1
            except:
                pass

print picks
1
追加された
+1まず、お時間をいただきありがとうございます。私はアルゴリズムを実際のコードに変換しましたが、私のコードにはバグがあるかもしれません。なぜなら、私に与える答えは[9,10]です。私は現在それをデバッグしようとしていて、もし何が間違っているのか分かっていればポストバックします。コード自体は理解するのが簡単ではないので、アルゴリズムで使用したのと同じ単語を使用してみましたので、共通の言語を話すことができます:)
追加された 著者 Legend,
@Legendあなたは歓迎です、パズルに感謝します!それはかなり面白いです。とにかく、私はコードを何回か走らせましたが、それを選ぶことができるセル9については何が特別なのか分かりません。どちらが最初に選ぶのですか?ちなみに、あなたが取り除いた鳥の複製ごとに、鳥を減らしているようです。それは私がそれが最初に9を選び、鳥を取り除いてから、10を選んでamountOfBirdsを-2にすると思わせる。しかし、たぶん私はちょうど十分にまだPythonコードを理解していない。 ^^ '
追加された 著者 Aberrant,