如何找到100个移动目标之间的最短路径? (包括现场演示。)

时间:2013-03-18 19:42:03

标签: javascript algorithm graph-theory

背景

这张照片说明了问题: square_grid_with_arrows_giving_directions

我可以控制红圈。目标是蓝色三角形。黑色箭头表示目标将移动的方向。

我希望以最少的步数收集所有目标。

每回合我必须向左/向右/向上或向下移动一步。

每转一圈,目标也会按照棋盘上显示的方向移动一步。

演示

我已经提出了问题here on 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 个答案:

答案 0 :(得分:23)

你找过文献了吗?我发现这些文件似乎可以分析你的问题:

更新1:

上述两篇论文似乎主要关注欧几里德度量的线性运动。

答案 1 :(得分:13)

贪婪的方法

评论中建议的一种方法是首先转到最近的目标。

我已经提供了一个演示版本,其中包含通过这种贪婪方法here计算的成本。

代码是:

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),偶尔甚至达到最佳距离。

这种方法非常有效,因此我可以提供一些周期来改善答案。

接下来我正在考虑使用蚁群方法来确定他们是否可以有效地探索解决方案空间。

蚁群方法

Ant colony method似乎对这个问题非常有效。这个答案中的链接现在比较了使用贪婪和蚁群方法时的结果。

这个想法是蚂蚁根据当前的信息素水平概率地选择他们的路线。每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;
}

结果

这种使用100次重复10次蚂蚁的蚁群方法仍然非常快(16个目标为37ms,穷举搜索为3700ms),看起来非常准确。

下表显示了使用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

蚂蚁方法似乎比贪婪方法好得多,而且往往非常接近最优。

答案 2 :(得分:8)

问题可以用广义旅行商问题来表示,然后转换成传统的旅行商问题。这是一个研究得很好的问题。 OP问题的最有效解决方案可能不比TSP的解决方案更有效,但绝不是确定的(我可能没有利用OP的问题结构的某些方面,这将允许更快的解决方案,如其周期性)。无论哪种方式,这都是一个很好的起点。

来自C. Noon & J.Bean, An Efficient Transformation of the Generalized Traveling Salesman Problem

  

广义旅行商问题(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_i将仅包含fish(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)未定义(即在鱼到达之前无法到达位置,可能是因为它在时间上倒退)。可以移除后一种类型的弧。
  • 需要添加一个额外的节点,表示播放器的位置,其中包含弧线和成本给所有其他节点。
然后解决这个问题将导致对每个节点子集的单次访问(即每条鱼获得一次)以获得成本最低的路径(即获得所有鱼的最短时间)。

本文接着描述了如何将上述公式转化为传统的旅行商问题,并随后用现有技术解决或近似。我没有仔细阅读细节,但是另一篇文章以一种宣称效率高的方式来做this one

复杂性存在明显问题。特别是,节点空间是无限的!这可以通过仅生成直到特定时间范围的节点来缓解。如果t是生成节点的时间步数,而f是鱼的数量,那么节点空间的大小将为t * f。时间j处的节点将具有最多(f - 1) * (t - j)个传出弧(因为它不能及时移回或移动到其自己的子集)。弧的总数将是t^2 * f^2弧的顺序。可以整理弧形结构,以利用鱼道最终是周期性的事实。鱼的周期长度的每个最小公分母都会重复它们的配置,所以可能会使用这个事实。

我对TSP的了解不足以说明这是否可行,我认为这并不意味着发布的问题必然 NP-hard ...但它是寻找最佳或有限解决方案的一种方法。

答案 3 :(得分:0)

我认为另一个问题是:

引用维基百科:

在数学中,Voronoi图是一种将空间划分为多个区域的方法。预先指定一组点(称为种子,站点或生成器),并且对于每个种子,将存在由与该种子相比更接近该种子的所有点组成的对应区域。

因此,您选择一个目标,按照其路径执行某些步骤并在那里设置种子点。与所有其他目标一起执行此操作,您将获得一个voroni图。根据您所在的区域,您可以移动到它的种子点。维奥拉,你得到了第一条鱼。现在重复这一步,直到你把所有这些都搞定。