[javascript] 100 개의 움직이는 표적 사이의 최단 경로를 어떻게 찾을 수 있습니까? (라이브 데모가 포함되어 있습니다.)

배경

이 그림은 문제를 보여줍니다.
square_grid_with_arrows_giving_directions

빨간색 원을 제어 할 수 있습니다. 대상은 파란색 삼각형입니다. 검은 색 화살표는 표적이 이동할 방향을 나타냅니다.

최소한의 단계로 모든 목표를 수집하고 싶습니다.

회전 할 때마다 왼쪽 / 오른쪽 / 위 또는 아래로 한 단계 씩 이동해야합니다.

매 턴마다 목표물은 보드에 표시된 지시에 따라 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가 어렵 기 때문에 이에 대한 최적의 답을 찾는 것이 부당하다는 것을 증명하려고합니다.



답변

문헌을 검색 했습니까? 귀하의 문제를 분석하는 것으로 보이는 다음 문서를 찾았습니다.

업데이트 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 개의 표적의 경우 최적 거리의 약 두 배이지만 때로는 훨씬 더 길고 (예 : * 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 번 반복하는이 개미 군집 방법은 여전히 ​​매우 빠르며 (완전한 검색의 경우 3700ms에 비해 16 개의 표적에 대해 37ms) 매우 정확 해 보입니다.

아래 표는 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

개미 방법은 탐욕스러운 것보다 훨씬 낫고 종종 최적에 매우 가깝습니다.


답변

문제는 Generalized Traveling Salesman Problem의 관점에서 표현 된 다음 기존 Traveling Salesman 문제로 변환 될 수 있습니다. 이것은 잘 연구 된 문제입니다. OP의 문제에 대한 가장 효율적인 솔루션이 TSP에 대한 솔루션보다 더 효율적이지 않을 수 있지만 결코 확실하지는 않습니다 (더 빠른 솔루션을 허용하는 OP의 문제 구조의 일부 측면을 활용하지 못할 수 있습니다. , 주기적 특성 등). 어느 쪽이든 좋은 출발점입니다.

가입일 C. 정오 및 J.Bean, 일반화 세일즈맨 문제를 효율적으로 변환 :

일반화 세일즈맨 문제 (GTSP)의 선택과 순서의 결정과 관련된 문제에 대한 유용한 모델이다. 문제의 비대칭 버전은 노드 N이있는 유 방향 그래프에서 정의되며, 호 A와 해당 호 비용 c의 벡터를 연결합니다. 노드는 m 개의 상호 배타적이고 완전한 노드 세트로 미리 그룹화됩니다. 연결 호는 다른 세트에 속하는 노드 사이에서만 정의됩니다. 즉, 인트라 세트 호가 없습니다. 정의 된 각 호에는 해당하는 음수가 아닌 비용이 있습니다. GTSP는 각 노드 세트에서 정확히 하나의 노드를 포함하는 최소 비용 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의 두 구성원에 manhattan(n_i, n_j)대해 두 노드 사이의 맨해튼 거리 및 time(n_i, n_j) 노드 사이의 시간 오프셋을 계산할 수 있습니다 .
  • 분리 된 부분 집합의 수 m은 물고기의 수와 같습니다. 분리 된 하위 집합 S_ifish(n) == i.
  • 두 노드의 경우 ij fish(n_i) != fish(n_j)다음 사이에 아크가 ij.
  • 노드 i와 노드 j 사이의 비용은 time(n_i, n_j)이거나 정의되지 않은 경우 time(n_i, n_j) < distance(n_i, n_j)(즉, 물고기가 도착하기 전에 위치에 도달 할 수 없습니다. 아마도 시간이 거꾸로되어 있기 때문일 것입니다). 이 후자 유형의 호는 제거 할 수 있습니다.
  • 다른 모든 노드에 호와 비용이있는 플레이어의 위치를 ​​나타내는 추가 노드를 추가해야합니다.

이 문제를 해결하면 최소한의 비용 (즉, 모든 물고기를 얻을 수있는 최소 시간)으로 경로에 대해 각 노드 하위 집합에 대한 단일 방문 (즉, 각 물고기를 한 번만 얻음)이 발생합니다.

이 논문은 위의 공식이 어떻게 전통적인 여행 세일즈맨 문제로 변환되고 이후에 기존 기술로 해결되거나 근사화 될 수 있는지 설명합니다. 나는 세부 사항을 읽지 않았지만 효율적이라고 선언하는 방식으로 이것을 수행하는 또 다른 논문은 이것 입니다.

복잡성에는 명백한 문제가 있습니다. 특히 노드 공간은 무한합니다! 이는 특정 시간대까지만 노드를 생성함으로써 완화 될 수 있습니다. 이 t노드를 생성 할 타임 스텝 f의 수이고 물고기의 수이면 노드 공간의 크기는 t * f. 시간에 노드에는 나가는 호가 j많아야합니다 (f - 1) * (t - j)(시간을 거슬러 올라가거나 자체 하위 집합으로 이동할 수 없기 때문에). 총 호의 수는 호의 순서입니다 t^2 * f^2. 호 구조는 아마도 물고기 경로가 궁극적으로 주기적이라는 사실을 활용하기 위해 정리 될 수 있습니다. 물고기는주기 길이의 최소 공통 분모마다 한 번씩 구성을 반복하므로이 사실을 사용할 수 있습니다.

나는 이것이 실현 가능한지 아닌지를 말할만큼 TSP에 대해 충분히 알지 못하며 게시 된 문제가 반드시 NP- 하드 라는 의미는 아니라고 생각 하지만 최적 또는 제한된 솔루션을 찾는 하나의 접근 방식입니다. .


답변

다른 접근 방식은 다음과 같습니다.

위키 백과 인용 :

수학에서 보로 노이 다이어그램은 공간을 여러 영역으로 나누는 방법입니다. 포인트 세트 (시드, 사이트 또는 생성자라고 함)가 미리 지정되어 있으며 각 시드에 대해 다른 어떤 시드보다 더 가까운 모든 포인트로 구성된 해당 영역이 있습니다.

그래서, 당신은 목표를 선택하고, 몇 단계를위한 경로를 따라 거기에 시드 포인트를 설정합니다. 다른 모든 대상에서도이 작업을 수행하면 보 로니 다이어그램이 생성됩니다. 당신이 어느 지역에 있는지에 따라 그것의 근원지로 이동합니다. 비올라, 당신은 첫 번째 물고기를 얻었습니다. 이제이 단계를 모두 반복 할 때까지 반복하십시오.


답변