89

バックグラウンド

この図は問題を示しています。 square_grid_with_arrows_giving_directions

赤い丸をコントロールできます。ターゲットは青い三角形です。黒い矢印は、ターゲットが移動する方向を示します。

最小ステップ数ですべてのターゲットを収集したい。

ターンごとに、左/右/上または下に 1 歩移動する必要があります。

ターンごとに、ボードに示されている方向に従って、ターゲットも 1 ステップ移動します。

デモ

この問題の再生可能なデモをGoogle appengine に掲載しました。

私の現在のアルゴリズムが最適ではないことを示すため、誰かが目標スコアを上回ることができれば非常に興味があります. (これを管理すると、お祝いのメッセージが出力されるはずです!)

問題

私の現在のアルゴリズムは、ターゲットの数に対して非常にうまくスケーリングしません。時間は指数関数的に増加し、16 匹の魚ではすでに数秒です。

ボードのサイズが 32*32 で、移動するターゲットが 100 個ある場合の答えを計算したいと思います。

質問

すべてのターゲットを収集するための最小ステップ数を計算するための効率的なアルゴリズム (理想的には Javascript) は何ですか?

私が試したこと

私の現在のアプローチはメモ化に基づいていますが、非常に遅く、常に最適なソリューションが生成されるかどうかはわかりません。

「与えられた一連のターゲットを収集し、特定のターゲットに到達するための最小ステップ数は?」というサブ問題を解決します。

部分問題は、以前に訪問したターゲットの各選択肢を調べることによって、再帰的に解決されます。以前のターゲットのサブセットをできるだけ早く収集し、最終的に到達した位置から現在のターゲットにできるだけ早く移動することが常に最適であると思います (ただし、これが有効な仮定であるかどうかはわかりません)。

これにより、n*2^n の状態が計算され、非常に急速に増大します。

現在のコードを以下に示します。

var DX=[1,0,-1,0];
var DY=[0,1,0,-1]; 

// Return the location of the given fish at time t
function getPt(fish,t) {
  var i;
  var x=pts[fish][0];
  var y=pts[fish][1];
  for(i=0;i<t;i++) {
    var b=board[x][y];
    x+=DX[b];
    y+=DY[b];
  }
  return [x,y];
}

// Return the number of steps to track down the given fish
// Work by iterating and selecting first time when Manhattan distance matches time
function fastest_route(peng,dest) {
  var myx=peng[0];
  var myy=peng[1];
  var x=dest[0];
  var y=dest[1];
  var t=0;
  while ((Math.abs(x-myx)+Math.abs(y-myy))!=t) {
    var b=board[x][y];
    x+=DX[b];
    y+=DY[b];
    t+=1;
  }
  return t;
}

// Try to compute the shortest path to reach each fish and a certain subset of the others
// key is current fish followed by N bits of bitmask
// value is shortest time
function computeTarget(start_x,start_y) {
  cache={};
  // Compute the shortest steps to have visited all fish in bitmask
  // and with the last visit being to the fish with index equal to last
  function go(bitmask,last) {
    var i;
    var best=100000000;
    var key=(last<<num_fish)+bitmask;
    if (key in cache) {
      return cache[key];
    }
    // Consider all previous positions
    bitmask -= 1<<last;
    if (bitmask==0) {
      best = fastest_route([start_x,start_y],pts[last]);
    } else {
      for(i=0;i<pts.length;i++) {
        var bit = 1<<i;
        if (bitmask&bit) {
          var s = go(bitmask,i);   // least cost if our previous fish was i
          s+=fastest_route(getPt(i,s),getPt(last,s));
          if (s<best) best=s;
        }
      }
    }
    cache[key]=best;
    return best;
  }
  var t = 100000000;
  for(var i=0;i<pts.length;i++) {
    t = Math.min(t,go((1<<pts.length)-1,i));
  }
  return t;
}

検討したこと

私が疑問に思ったいくつかのオプションは次のとおりです。

  1. 中間結果のキャッシュ。距離の計算では多くのシミュレーションが繰り返され、中間結果がキャッシュされる可能性があります。
    ただし、これで指数関数的な複雑さがなくなるとは思いません。

  2. A* 検索アルゴリズムですが、適切な許容可能なヒューリスティックとは何か、これが実際にどの程度効果的かはわかりません。

  3. 巡回セールスマン問題の優れたアルゴリズムを調査し、それらがこの問題に適用されるかどうかを確認します。

  4. 問題が NP 困難であることを証明しようとするため、最適な解を求めるのは不合理です。

4

4 に答える 4

24

文献を検索しましたか?あなたの問題を分析しているように見えるこれらの論文を見つけました:

更新 1:

上記の 2 つの論文は、ユークリッド計量の線形移動に集中しているようです。

于 2013-03-18T22:51:48.757 に答える
13

貪欲な方法

コメントで提案されている 1 つのアプローチは、最初に最も近いターゲットに移動することです。

この貪欲な方法で計算されたコストを含むデモのバージョンをここに掲載しました。

コードは次のとおりです。

function greedyMethod(start_x,start_y) {
  var still_to_visit = (1<<pts.length)-1;
  var pt=[start_x,start_y];
  var s=0;
  while (still_to_visit) {
    var besti=-1;
    var bestc=0;
    for(i=0;i<pts.length;i++) {
      var bit = 1<<i;
      if (still_to_visit&bit) {
        c = fastest_route(pt,getPt(i,s));
        if (besti<0 || c<bestc) {
          besti = i;
          bestc = c;
        }
      }
    }
    s+=c;
    still_to_visit -= 1<<besti;
    pt=getPt(besti,s);
  }
  return s;
}

ターゲットが 10 個の場合、最適な距離の約 2 倍ですが、場合によってはそれ以上 (たとえば *4) になり、場合によっては最適な距離に達することさえあります。

このアプローチは非常に効率的であるため、答えを改善するためにいくつかのサイクルを実行できます。

次に、アリのコロニー法を使用して、解空間を効果的に探索できるかどうかを検討しています。

アリコロニー法

Ant コロニー法は、この問題に対して非常にうまく機能しているようです。この回答のリンクは、貪欲な方法とアリのコロニー方法の両方を使用した場合の結果を比較しています。

アイデアは、アリが現在のフェロモンのレベルに基づいて確率的にルートを選択するというものです。10 回の試行ごとに、見つけた最短の軌跡に沿って追加のフェロモンを配置します。

function antMethod(start_x,start_y) {
  // First establish a baseline based on greedy
  var L = greedyMethod(start_x,start_y);
  var n = pts.length;
  var m = 10; // number of ants
  var numrepeats = 100;
  var alpha = 0.1;
  var q = 0.9;
  var t0 = 1/(n*L);

  pheromone=new Array(n+1); // entry n used for starting position
  for(i=0;i<=n;i++) {
    pheromone[i] = new Array(n);
    for(j=0;j<n;j++)
      pheromone[i][j] = t0; 
  }

  h = new Array(n);
  overallBest=10000000;
  for(repeat=0;repeat<numrepeats;repeat++) {
    for(ant=0;ant<m;ant++) {
      route = new Array(n);
      var still_to_visit = (1<<n)-1;
      var pt=[start_x,start_y];
      var s=0;
      var last=n;
      var step=0;
      while (still_to_visit) {
        var besti=-1;
        var bestc=0;
        var totalh=0;
        for(i=0;i<pts.length;i++) {
          var bit = 1<<i;
          if (still_to_visit&bit) {
            c = pheromone[last][i]/(1+fastest_route(pt,getPt(i,s)));
            h[i] = c;
            totalh += h[i];
            if (besti<0 || c>bestc) {
              besti = i;
              bestc = c;
            }
          }
        }
        if (Math.random()>0.9) {
          thresh = totalh*Math.random();
          for(i=0;i<pts.length;i++) {
            var bit = 1<<i;
            if (still_to_visit&bit) {
              thresh -= h[i];
              if (thresh<0) {
                besti=i;
                break;
              }
            }
          }
        }
        s += fastest_route(pt,getPt(besti,s));
        still_to_visit -= 1<<besti;
        pt=getPt(besti,s);
        route[step]=besti;
        step++;
        pheromone[last][besti] = (1-alpha) * pheromone[last][besti] + alpha*t0;
        last = besti;
      }
      if (ant==0 || s<bestantscore) {
        bestroute=route;
        bestantscore = s;
      }
    }
    last = n;
    var d = 1/(1+bestantscore);
    for(i=0;i<n;i++) {
      var besti = bestroute[i];
      pheromone[last][besti] = (1-alpha) * pheromone[last][besti] + alpha*d;
      last = besti;
    }
    overallBest = Math.min(overallBest,bestantscore);
  }
  return overallBest;
}

結果

10 匹のアリの 100 回の繰り返しを使用するこのアリ コロニー メソッドは、依然として非常に高速であり (網羅的検索の 3700 ミリ秒と比較して、16 ターゲットの場合は 37 ミリ秒)、非常に正確に見えます。

以下の表は、16 個のターゲットを使用した 10 回の試行の結果を示しています。

   Greedy   Ant     Optimal
   46       29      29
   91       38      37
  103       30      30
   86       29      29
   75       26      22
  182       38      36
  120       31      28
  106       38      30
   93       30      30
  129       39      38

アリの方法は貪欲な方法よりもはるかに優れており、多くの場合、最適に非常に近いようです。

于 2013-03-20T20:22:07.280 に答える
8

問題は、一般化された巡回セールスマン問題の観点から表され、その後、従来の巡回セールスマン問題に変換される場合があります。これはよく研究された問題です。OPの問題に対する最も効率的な解決策は、TSPに対する解決策よりも効率的ではない可能性がありますが、決して確実ではありません(おそらく、より迅速な解決策を可能にするOPの問題構造のいくつかの側面を利用できていません。 、その周期的な性質など)。いずれにせよ、それは良い出発点です。

C. Noon&J.Beanから、一般化された巡回セールスマン問題の効率的な変換

一般化された巡回セールスマン問題(GTSP)は、選択と順序の決定を含む問題の有用なモデルです。問題の非対称バージョンは、ノードNを持ち、アークAと対応するアークコストcのベクトルを接続する有向グラフで定義されます。ノードは、相互に排他的で網羅的なm個のノードセットに事前にグループ化されています。接続アークは、異なるセットに属するノード間でのみ定義されます。つまり、セット内アークはありません。定義された各アークには、対応する非負のコストがあります。GTSPは、各ノードセットから正確に1つのノードを含む最小コストのm-arcサイクルを見つける問題として説明できます。

OPの問題について:

  • の各メンバーNは、特定の時間における特定の魚の場所です。これを(x, y, t)、として表します。ここ(x, y)で、はグリッド座標でtあり、は魚がこの座標にいる時間です。OPの例の左端の魚の場合、これらの最初のいくつか(1ベース)は次(3, 9, 1), (4, 9, 2), (5, 9, 3)のとおりです。魚が右に移動するとき。
  • Nの任意のメンバーについてfish(n_i)、ノードによって表される魚のIDを返します。Nの任意の2つのメンバーについてmanhattan(n_i, n_j)、2つのノード間のマンハッタン距離、およびtime(n_i, n_j)ノード間の時間オフセットを計算できます。
  • 互いに素なサブセットの数mは、魚の数と同じです。互いに素なサブセットS_iは、のノードのみで構成されfish(n) == iます。
  • 2つのノードの場合、ij fish(n_i) != fish(n_j)の間にアークがiありjます。
  • ノードiとノードjの間のコストはtime(n_i, n_j)、または未定義ですtime(n_i, n_j) < distance(n_i, n_j)(つまり、魚がそこに着く前にその場所に到達できない場合、おそらく時間的に遅れているためです)。この後者のタイプのアークは削除できます。
  • アークと他のすべてのノードへのコストでプレーヤーの場所を表す追加のノードを追加する必要があります。

この問題を解決すると、最小のコスト(つまり、すべての魚を取得するための最小の時間)でパスを取得するために、各ノードサブセットに1回アクセスする(つまり、各魚を1回取得する)ことになります。

このホワイトペーパーでは、上記の定式化を従来の巡回セールスマン問題に変換し、その後、既存の手法で解決または近似する方法について説明します。私は詳細を読んでいませんが、効率的であると宣言する方法でこれを行う別の論文はこれです。

複雑さには明らかな問題があります。特に、ノードスペースは無限大です!これは、特定の期間までノードを生成するだけで軽減できます。tがノードを生成するタイムステップの数でありf、が魚の数である場合、ノードスペースのサイズはになりますt * f。ある時点でのノードにjは、最大で(f - 1) * (t - j)発信アークがあります(時間内または独自のサブセットに戻ることができないため)。アークの総数はアークの順になりt^2 * f^2ます。魚の小道が最終的に周期的であるという事実を利用するために、弧の構造をおそらく片付けることができます。魚は、サイクル長の最小公分母ごとに構成を繰り返すので、おそらくこの事実を使用できます。

TSPについて、これが実行可能かどうかを判断するのに十分なことはわかりません。また、投稿された問題が必ずしもNP困難であることを意味するとは思いません...しかし、これは最適な解決策または制限された解決策を見つけるための1つのアプローチです。 。

于 2013-03-20T16:16:47.793 に答える
1

別のアプローチは次のようになると思います:

  • ターゲットのパスを計算します - 予測。
  • ボロノイ図を使うより

ウィキペディアを引用:

数学では、ボロノイ図は空間をいくつかの領域に分割する方法です。ポイントのセット (シード、サイト、またはジェネレーターと呼ばれる) が事前に指定され、各シードに対して、他のどのシードよりもそのシードに近いすべてのポイントで構成される対応する領域が存在します。

したがって、ターゲットを選択し、いくつかのステップでそのパスをたどり、そこにシード ポイントを設定します。他のすべてのターゲットでもこれを行うと、ボロニ ダイアグラムが得られます。あなたがいるエリアに応じて、そのシードポイントに移動します。ビオラ、あなたは最初の魚を手に入れました。すべての咳が出るまで、この手順を繰り返します。

于 2013-03-20T07:06:10.127 に答える