【问题标题】:How to compute optimal paths for traveling salesman bitonic tour?如何计算旅行商双调旅行的最佳路径?
【发布时间】:2010-10-26 21:00:16
【问题描述】:

更新

经过更多阅读,可以给出以下递归关系的解决方案:

(a) When i = 1 and j = 2, l(i; j) = dist(pi; pj )
(b) When i < j - 1; l(i; j) = l(i; j - 1) + dist(pj-1; pj)
(c) When i = j - 1 and j > 2, min 1<=k<i (l(k; i) + dist(pk; pj ))

现在这开始变得有意义了,除了 C 部分。我将如何确定最小值 k?我想这意味着您可以遍历所有可能的 k 值并仅存储 ( l(k,i) + dist(pk,pj) 的最小结果?


是的,这绝对是我在学校学习的一个问题。我们正在研究旅行商问题的双调旅行。

无论如何,假设我有 5 个顶点 {0,1,2,3,4}。我知道我的第一步是按 x 坐标增加的顺序对它们进行排序。从那里开始,我对如何使用动态编程完成这一点有点困惑。

我正在阅读我应该扫描已排序节点的列表,并为两个部分(初始路径和返回路径)保持最佳路径。我对如何计算这些最佳路径感到困惑。例如,我如何知道我是否应该在初始路径或返回路径中包含给定节点,因为它不能同时包含在两者中(端点除外)。回想一下动态编程中的斐波那契,你基本上从你的基本情况开始,然后继续前进。我想我要问的是如何开始解决双调旅行商问题?

对于像斐波那契数字这样的东西,动态规划方法非常清楚。但是,我不知道我是否只是过于密集或什么,但我很困惑试图解决这个问题。

感谢收看!

注意:我不是在寻找完整的解决方案,但至少有一些好的提示可以帮助我入门。例如,如果这是斐波那契问题,可以说明前几个数字是如何计算的。请让我知道如何改进这个问题。

【问题讨论】:

标签: algorithm graph dynamic-programming


【解决方案1】:

好的,动态规划解决方案中的关键概念是:

  • 您预先计算较小的问题
  • 您有一条规则,可以让您结合较小的问题来为较大的问题找到解决方案
  • 您拥有问题的已知属性,可以证明解决方案在某种最优性度量下确实是最优的。 (在这种情况下,最短。)

双调游的本质属性是坐标系中的一条垂直线最多穿过封闭多边形的一侧。那么,什么是正好两点的双音之旅呢?显然,任何两点形成一个(退化的)双音巡演。三点有两个双调旅行(“顺时针”和“逆时针”)。

现在,您如何预先计算各种较小的双音旅行并将它们组合起来,直到您包含所有点并且仍然有双音旅行?


好的,您的更新走在了正确的轨道上。但是现在,在动态规划解决方案中,您如何处理它自下而上:预计算和memoize(不是“记忆”)最优子问题。

【讨论】:

    【解决方案2】:

    澄清您的算法。

    l(i,j) 递归函数应该计算双调旅行i -&gt; 1 -&gt; j 访问小于i 的所有节点的最小距离。所以,初始问题的解决方案是l(n,n)!

    重要提示:

    1. 我们可以假设节点按其 x 坐标排序并相应标记 (p1.x &lt; p2.x &lt; p3.x ... &lt; pn.x)。如果它们没有被排序,我们可以在O(nlogn)时间对它们进行排序。

    2. l(i,j) = l(j,i)。原因是在 lhs 中,我们有一个最佳的 i -&gt;...-&gt; 1 -&gt; ... -&gt; j 游览。然而,向后遍历这条路线会给我们相同的距离,并且不会破坏双音特性。

    现在是简单的案例(注意变化!):

    (a) When i = 1 and j = 2, l(i; j) = dist(pi; pj ) = dist(1,2)
    

    在这里,我们有以下游览:1-&gt;1-&gt;...-&gt;2。这相当于路径1-&gt;...-&gt;2 的长度。由于点是按它们的.x 坐标排序的,12 之间没有点,所以连接它们的直线将是最优的。 (在2 之前选择任意数量的其他访问点会导致路径更长!)

    (b) When i < j - 1; l(i; j) = l(i; j - 1) + dist(pj-1; pj)
    

    在这种情况下,j-1 必须位于路径 1 -&gt; ... -&gt; j 的部分,因为部分 i -&gt; ... -&gt; 1 不能包含索引大于 i 的节点。因为路径1 -&gt; ... -&gt; j 中的所有节点都按索引的递增顺序排列,所以j-1j 之间不可能没有。所以,这相当于游览:i -&gt; ... -&gt; 1 -&gt; .... -&gt; j-1 -&gt; j,相当于l(i,j-1) + dist(pj-1,pj)

    Anf 有趣的部分终于来了:

    (c) When i = j - 1 or i = j, min 1<=k<i (l(k; i) + dist(pk; pj ))
    

    这里我们知道我们必须从i1,但是后面的扫描没有任何线索!这里的关键思想是我们必须在我们的反向路由上考虑j 之前的节点。它可能是从1j-1 的任何一个节点!让我们假设这个节点是k。 现在我们有一个游览:i -&gt; ... -&gt; 1 -&gt; .... -&gt; k -&gt; j,对吧?这次旅行的费用是l(i,k) + dist(pk,pj)

    希望你明白了。

    实施。

    您将需要一个二维数组,例如BT[1..n][1..n]。设i 为行索引,j 为列索引。我们应该如何填写这张表?

    在第一行中,我们知道BT[1][1] = 0BT[1][2] = d(1,2),因此我们只剩下i,j 属于(b) 类别的索引。

    在剩下的行中,我们从对角线到最后填充元素。

    这是一个示例 C++ 代码(未经测试):

    void ComputeBitonicTSPCost( const std::vector< std::vector<int> >& dist, int* opt ) {
      int n = dist.size();
      std::vector< std::vector< int > > BT;
      BT.resize(n);
      for ( int i = 0; i < n; ++i )
        BT.at(i).resize(n);
    
      BT.at(0).at(0) = 0;  // p1 to p1 bitonic distance is 0
      BT.at(0).at(1) = dist.at(0).at(1);  // p1 to p2 bitonic distance is d(2,1)
    
      // fill the first row
      for ( int j = 2; j < n; ++j )
        BT.at(0).at(j) = BT.at(0).at(j-1) + dist.at(j-1).at(j);
    
      // fill the remaining rows
      int temp, min;  
      for ( int i = 1; i < n; ++i ) {
        for ( int j = i; j < n; ++j ) {
          BT.at(i).at(j) = -1;
          min = std::numeric_limits<int>::max();
          if ( i == j || i == j -1 ) {
            for( int k = 0; k < i; ++k ) {
              temp = BT.at(k).at(i) + dist.at(k).at(j);
              min = ( temp < min ) ? temp : min;
            }        
            BT.at(i).at(j) = min;        
          } else {
            BT.at(i).at(j) = BT.at(i).at(j-1) + dist.at(j-1).at(j);
          }
        }
      }
    
      *opt = BT.at(n-1).at(n-1);
    }
    

    【讨论】:

      猜你喜欢
      • 2020-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多