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:

上述两篇论文似乎专注于欧几里得度量的线性运动。

于 2013-03-18T22:51:48.757 回答
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),有时甚至会达到最佳距离。

这种方法非常有效,因此我可以负担一些周期来改进答案。

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

蚁群法

蚁群方法似乎可以很好地解决这个问题。这个答案中的链接现在比较了同时使用贪婪和蚁群方法时的结果。

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

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

于 2013-03-20T20:22:07.280 回答
8

该问题可以用广义旅行商问题来表示,然后转换为传统的旅行商问题。这是一个经过充分研究的问题。OP 问题的最有效解决方案可能并不比 TSP 的解决方案更有效,但绝不是肯定的(我可能未能利用 OP 问题结构的某些方面来实现更快的解决方案,例如其周期性)。无论哪种方式,这是一个很好的起点。

来自C. Noon 和 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_i将仅包含 的节点fish(n) == i
  • 如果对于两个节点i,则和j fish(n_i) != fish(n_j)之间存在弧。ij
  • time(n_i, n_j)节点 i和节点 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 难的……但这是寻找最优或有界解决方案的一种方法.

于 2013-03-20T16:16:47.793 回答
1

我认为另一种方法是:

  • 计算目标的路径 - 预测。
  • 比使用Voronoi 图

引用维基百科:

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

所以,你选择一个目标,按照它的路径进行一些步骤,并在那里设置一个种子点。对所有其他目标也这样做,你会得到一个 voroni 图。根据您所在的区域,您将移动到它的种子点。维奥拉,你得到了第一条鱼。现在重复这个步骤,直到你把它们都咳出来。

于 2013-03-20T07:06:10.127 回答