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


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难题,因此不合理地寻求最佳答案。


1
我会去#4,然后再去#3:如果有足够大的电路板,它会很好地模仿TSP。
John Dvorak

2
据我所知,TSP具有欧几里得度量和曼哈顿度量(正方形网格)的NP难点。
John Dvorak

1
如果通过简单的树形搜索进行操作,是的,它将是指数级的。但是,如果您在每个步骤中都能找到一个体面的启发式方法,它可能并不是真正的最佳选择,但可能会非常好。一种可能的启发式方法是,查看当前的鱼类,可以最快地找到哪一条?可能是次要的启发式方法,我能最快到达哪两条鱼?
Mike Dunlavey

2
@MikeDunlavey与贪婪的TSP算法相对应,并且在实践中效果很好。寻找最近的鱼似乎是个好主意
John Dvorak

1
+1是我最近见过的关于内容和结构的最佳问题之一。
surfitscrollit 2013年

Answers:


24

您搜索文献了吗?我发现这些论文似乎可以分析您的问题:

更新1:

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


谢谢-我没有看过那些论文,但它们看起来很相关。我将看看是否可以使遗传算法适合我的情况,并将其与蛮力方法的结果进行比较。
彼得·德里瓦兹

13

贪婪的方法

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

我已经提出了一个版本,其中包括通过这种贪心法计算的成本演示在这里

代码是:

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

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


真好 您可能尚未从详尽的搜索中获得最佳结果(或可能由于其难处理性而从未获得过!),但很有趣的是,看到具有相同目标数目的蚁群如何随着板子尺寸(32x32)缩放。
timxyz

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
  • 如果对于两个节点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难题的……但这是寻找最佳或有界解决方案的一种方法。


谢谢,这对我来说是新的,非常有趣。我认为我应该能够将此转换与Christofides算法结合使用,以在最佳近似值的3/2内有效地找到一个解决方案。如果可以正常使用,我会将产生的路由添加到演示页面。
彼得·德里瓦兹

嗯,我认为我的计划存在一个问题,尽管我最初的问题是一个完整的图形,该图形满足度量上的适当不等式,但是所描述的变换导致图形不完整,因此Christofides算法不再适用。无论如何,感谢您的有趣观点。
彼得·德里瓦兹

是的,我忘记提及三角不等式不再成立了。对于启发式解决方案和更一般的近似而言,这是一个很好的起点。
timxyz

1

我认为另一个办法是:

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

引用维基百科:

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

因此,您选择一个目标,按照其路径进行一些步骤,并在那里设置种子点。同样对所有其他目标执行此操作,您将获得伏洛尼图。根据您所在的区域,移至其种子点。中提琴,你有第一条鱼。现在重复此步骤,直到您全部治愈为止。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.