web-dev-qa-db-ja.com

100個の移動ターゲット間の最短経路を見つけるにはどうすればよいですか? (ライブデモが含まれています。)

バックグラウンド

この図は問題を示しています。 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困難であり、したがってそれに対する最適な答えを探すのは不合理であることを証明しようとしています。

89
Peter de Rivaz

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

更新1:

上記の2つの論文は、ユークリッド距離の直線運動に焦点を当てているようです。

23
uldall

欲張り法

コメントで提案されている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など)、場合によっては最適距離に達することもあります。

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

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

アリの巣法

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

アリは現在のフェロモンのレベルに基づいて確率的に経路を選択するという考え方です。 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回繰り返すこのアリのコロニーの方法は、依然として非常に高速であり(16のターゲットで37ミリ秒、徹底的な検索では3700ミリ秒)、非常に正確に見えます。

次の表は、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

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

13
Peter de Rivaz

問題は、一般化された巡回セールスマン問題の観点から表され、その後、従来の巡回セールスマン問題に変換される場合があります。これはよく研究された問題です。 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つのメンバーについて、2つのノード間のマンハッタン距離についてはmanhattan(n_i, n_j)を計算し、ノード間の時間オフセットについては_time(n_i, n_j_)を計算できます。
  • 互いに素なサブセットの数mは、魚の数と同じです。互いに素なサブセット_S_i_は、fish(n) == iの対象となるノードのみで構成されます。
  • 2つのノードijfish(n_i) != fish(n_j)の場合、ijの間に円弧があります。
  • ノード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_アークのオーダーになります。魚の小道が最終的に周期的であるという事実を利用するために、弧の構造をおそらく片付けることができます。魚は、サイクル長の最小公分母ごとに1回構成を繰り返すため、おそらくこの事実を使用できます。

TSPについて、これが実行可能かどうかを判断するのに十分な知識がありません。また、投稿された問題が必然的にであるとは限りません。 NP困難...しかし、これは最適なソリューションまたは制限されたソリューションを見つけるための1つのアプローチです。

8
timxyz

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

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

ウィキペディアを引用:

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

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

0