A *允许的启发式在网格上滚动

时间:2013-05-14 15:57:42

标签: algorithm path-finding a-star heuristics

我需要一些帮助,为以下问题找到一个好的启发式方法:

  

您获得了 R -by- C 网格和六面骰子。让 start 和    end 是此网格上的两个不同的单元格。找到从startend的路径   当模具沿着路径转动时,模具面朝上的总和就是   最小的。

     

骰子的起始方向如下(“2”朝南):

     

enter image description here

我建模这个问题的方法是将骰子的面值视为图中边的成本。图形的顶点具有(row, col, die)形式(即网格中的位置和模具的当前状态/方向)。顶点不仅仅是(row, col)的原因是因为你可以使用骰子的多个配置/方向结束相同的单元格。

我用A *来找到问题的解决方案;给出的答案是正确的,但效率不高。我已经确定问题是我正在使用的启发式问题。目前我正在使用曼哈顿距离,这显然是可以接受的。如果我将启发式乘以常量,则不再允许:它运行得更快,但并不总能找到正确的答案。

我需要一些帮助才能找到比曼哈顿距离更好的启发式方法。

5 个答案:

答案 0 :(得分:10)

好吧,我会在这里添加我的评论,因为它比@larsmans当前得票最高的答案更优化 - 但是,我确信必须有更好的东西(因此是赏金)。


  

如果我将启发式乘以常数,则不再允许

我能想到的最好的是(manhattenDistance/3)*6 + (manhattenDistance%3),其中/是整数除法,%是mod。这是有效的,因为在没有反向跟踪的任何3个动作中,所有三个数字都是唯一的,因此我们可能具有的最低总和是1 + 2 + 3 = 6 (%3简单地添加任何额外的,非多个三个动作)


[编辑] 正如@GrantS在上面的评论中指出的那样,在1时添加额外的manhattenDistance%3 == 2可以略微改进我的启发式。没有条件,这很容易做到:(manhattenDistance/3)*6 + (manhattenDistance%3)*3/2

答案 1 :(得分:10)

主要编辑3:证明最佳可接受启发式应基于3.5m

3.5m到曼哈顿的距离,长途旅行的平均成本必须接近m。因此,最好的可接受启发式应该是3.5m加上或减去一些小常数。

这样做的原因是,无论何时向某个方向移动,例如,从脸部x1移动x,相同方向的下一步移动,面对x2必须满足x1 + x2 = 7 }。这是因为垂直方向上的任何移动都会使面x2的方向相同。考虑从左到右旋转模具 - 无论你做多少次旋转,前后面都保持不变。相反,如果您将模具前后旋转,左右两面保持不变。

最简单的方法是通过一些示例来看这一点(所有这些都从问题中描绘的配置开始)

   6
2453
1

在这里你可以看到我们从y1=1开始,然而我们在x方向上移动了很多次,y方向的下一步移动必须是y2=6,所以{{ 1}}。 (同样在x方向,有一个简单的y1+y2=72+5 = 7配对。

另一个例子是

4+3 = 7

在这个例子中,我们从 35 26 14 开始,然而无论多少次我们在y方向上移动,x方向的下一步移动必须是x1=1。 (另外,我们在y方向看到x2=6的对,在x方向看到4+3=7。我们知道在这种情况下,x方向的下一步移动必须是{{1} },y方向的下一步移动必须是2+5=7。)

这一切都假设它永远不值得回溯,但希望这可以视为阅读。

下面的原始帖子只是填写了一些细节,说明如何调整4的估算值,以考虑短期内被殴打的能力。

作为旁注,正如我刚刚对OP所评论的那样,可能根本不需要A *搜索。简单地选择由4长水平件和4长垂直件组成的路径应该是有意义的,比如说,这是最佳的。然后使用基于方向和x-y偏移的搜索或查找表来构成余数。 (但问题是要求允许的启发式,所以我要留下我的答案。)

主要编辑2:总结原始实证工作,并考虑以下评论

从长远来看,如上所述,您的平均每次移动费用为3.5。在探索下面的数据时,也可以凭经验看出这一点。

这给出了1的天真估计,其中3.5m是曼哈顿距离。然而,这是一个高估,因为在短期内 可能比平均值更好。一个很好的假设就是探索如何避免使用任何大于3的面。

  • 如果我们从面部 1 开始,我们可以在前2个动作中使用面部2和3, 2 3.5m预测更好。< / LI>
  • 如果我们从面部 2 开始,我们可以在前两个动作中使用面部1和3, 3 m预测更好。< / LI>
  • 如果我们从脸部 3 开始,我们可以在前2个动作中使用面部1和2, 4 3.5m预测更好。< / LI>
  • 如果我们从面部 4,5或6 开始,我们可以在前三次移动中使用面部1,2和3, 4.5 移动得比{更好} {1}}预测。

如BlueRaja - Danny Pflughoeft所建议的那样,这个假设可以通过简单地运行下面的脚本来确认死亡的每个起始可能性,从而凭经验证实。因此,一个简单的可接受统计信息是3.5m,其中3.5m3.5m是起始面。但这有点笨拙,为3.5m - k的小值给出负数。编写一个程序化版本很容易,考虑到你只有1或2或3个动作,见下文

k = max(f+1, 4.5)

f的搜索空间中运行此功能时,此功能会低估0到6之间的实际成本,中位数为0.5或1.5,具体取决于起始面。

主要编辑1:原始帖子

我的基本想法是探索数据会很好。所以我去Dijkstra's algorithm看看解决方案的空间是什么样的。我发现的是支持已经说过的话。曼哈顿距离的某些因素是合适的,但可能有一些理由要求高于1.5的因子。这可以通过成本与初始x y位置偏离的轮廓图的形状很好地表示。

cost against deviation from initial x y position

这是一个线框图 - 说实话,这只是为了眼睛糖果。

enter image description here

有趣的是,如果您为曼哈顿距离(man)的数据添加另一列,并将成本(v)与R中的曼哈顿距离进行回归,则可获得以下内容

m

即。它告诉你,对于你水平或垂直移动的每一个动作,你的成本是3.4991861,或者接近3.5。这恰好是1到6的平均值,所以我的直觉是数据告诉我们,平均而言,在长距离上同等地使用模具的所有面是最有效的。在短距离内,您可以更加优化。

我使用 static double Adm(int x, int y, int face /* start face */, out int m) { double adm = 0; m = Math.Abs(x) + Math.Abs(y); if (m >= 1) { if (face == 1) adm += 2; else adm += 1; m--; } if (m >= 1) { if (face <= 2) adm += 3; else adm += 2; m--; } if (m >= 1 && face >=4) { // 4,5,6: we can still use a 3 without backtracking adm += 3; m--; } adm += 3.5 * m; return adm; } 作为估算值​​|x|,|y| <= 100。这似乎工作正常。当我从中减去实际成本时,我得到-0.5作为最高值。

Coefficients:
          Estimate Std. Error  t value Pr(>|t|)    
(Intercept) -0.6408087  0.0113650   -56.38   <2e-16
df$man       3.4991861  0.0001047 33421.66   <2e-16

然而,A *搜索必须适用于所有配置,包括骰子不在原始配置中的开始之后,因此常量3.5man - k不能低于k = 2.5一般来说。它需要被提升,例如至> summary(df$est - df$v) Min. 1st Qu. Median Mean 3rd Qu. Max. -6.500 -2.500 -2.000 -1.777 -1.000 -0.500 ,或依赖于骰子的配置,如另一个答案所示。

我很可能在所有这些中犯了一些可怕的错误,所以我把代码放在下面。就像我说的那样,即使我的结果不是这样,我认为生成数据和调查数据的方法也是合理的。

首先是结果文件的一些行。

  

17,-100,410

     

17,-99,406

     

17,-98,403

     

17,-97,399

     

17,-96,396

C#代码

k

以下R代码

2.5

答案 2 :(得分:7)

  

如果我将启发式乘以常数,则不再允许

如果你摆脱一些角落案件就可以了。设 d 为曼哈顿距离,并观察骰子在路径的两个后续步骤中永远不会使其1面朝上。因此,如果你还没有达到目标:

  • 第一步的成本至少为1;
  • 如果1面朝上,则至少为2(同样适用于6);
  • 路径的其余部分至少与一系列1-2次交替一样昂贵,费用为1.5×( d - 1)。

所以可接受的启发式是

if d == 0 then
    h := 0
else if die == 1 or die == 6 then
    h := 2 + 1.5 × (d - 1)
else
    h := 1 + 1.5 × (d - 1)

答案 3 :(得分:6)

这是我的算法应用于Paul的300x300网格示例,从(23,25)开始到(282,199)结束。它在0.52秒内找到最小路径和总和(1515,比保罗1517的结果小2点)。带有查找表而不是计算小部分的版本需要0.13秒。

Haskell代码:

import Data.List (minimumBy)
import Data.Ord (comparing)
import Control.Monad (guard)

rollDie die@[left,right,top,bottom,front,back] move
  | move == "U" = [left,right,front,back,bottom,top]
  | move == "D" = [left,right,back,front,top,bottom]
  | move == "L" = [top,bottom,right,left,front,back]
  | move == "R" = [bottom,top,left,right,front,back]

dieTop die = die!!2

--dieStartingOrientation = [4,3,1,6,2,5] --left,right,top,bottom,front,back

rows = 300
columns = 300

paths (startRow,startColumn) (endRow,endColumn) dieStartingOrientation = 
  solve (dieTop dieStartingOrientation,[]) [(startRow,startColumn)] dieStartingOrientation where
    leftBorder = max 0 (min startColumn endColumn)
    rightBorder = min columns (max startColumn endColumn)
    topBorder = endRow
    bottomBorder = startRow
    solve result@(cost,moves) ((i,j):pathTail) die =
      if (i,j) == (endRow,endColumn)
         then [(result,die)]
         else do
           ((i',j'),move) <- ((i+1,j),"U"):next
           guard (i' <= topBorder && i' >= bottomBorder && j' <= rightBorder && j' >= leftBorder)
           solve (cost + dieTop (rollDie die move),move:moves) ((i',j'):(i,j):pathTail) (rollDie die move) 
        where next | null pathTail            = [((i,j+1),"R"),((i,j-1),"L")]
                   | head pathTail == (i,j-1) = [((i,j+1),"R")]
                   | head pathTail == (i,j+1) = [((i,j-1),"L")]
                   | otherwise                = [((i,j+1),"R"),((i,j-1),"L")]

--300x300 grid starting at (23, 25) and ending at (282,199)

applicationNum = 
  let (r,c) = (282-22, 199-24)
      numRowReductions = floor (r/4) - 1
      numColumnReductions = floor (c/4) - 1
      minimalR = r - 4 * fromInteger numRowReductions
      minimalC = c - 4 * fromInteger numColumnReductions
  in (fst . fst . minimumBy (comparing fst) $ paths (1,1) (minimalR,minimalC) [4,3,1,6,2,5])
     + 14*numRowReductions + 14*numColumnReductions

applicationPath = [firstLeg] ++ secondLeg ++ thirdLeg
               ++ [((0,["R"]),[])] ++ [minimumBy (comparing fst) $ paths (1,1) (2,4) die2] 
    where
      (r,c) = (282-22, 199-24) --(260,175)
      numRowReductions = floor (r/4) - 1
      numColumnReductions = floor (c/4) - 1
      minimalR = r - 4 * fromInteger numRowReductions
      minimalC = c - 4 * fromInteger numColumnReductions
      firstLeg = minimumBy (comparing fst) $ paths (1,1) (minimalR,minimalC) [4,3,1,6,2,5]
      die0 = snd firstLeg
      secondLeg = tail . foldr mfs0 [((0,["R"]),die0)] $ [1..numColumnReductions - 1]
      die1 = snd . last $ secondLeg
      thirdLeg = tail . foldr mfs1  [((0,[]),die1)] $ [1..numRowReductions - 3 * div (numColumnReductions - 1) 4 - 1]
      die2 = rollDie (snd . last $ thirdLeg) "R"
      mfs0 a b = b ++ [((0,["R"]),[])] ++ [minimumBy (comparing fst) $ paths (1,1) (4,4) (rollDie (snd . last $ b) "R")]
      mfs1 a b = b ++ [((0,["U"]),[])] ++ [minimumBy (comparing fst) $ paths (1,1) (4,1) (rollDie (snd . last $ b) "U")]

<强>输出:

*Main> applicationNum
1515

*Main> applicationPath
[((31,["R","R","R","R","U","U","R","U","R"]),[5,2,1,6,4,3])
,((0,["R"]),[]),((25,["R","R","R","U","U","U"]),[3,4,1,6,5,2])
,((0,["R"]),[]),((24,["R","U","R","R","U","U"]),[5,2,1,6,4,3])
................((17,["R","R","R","U"]),[5,2,1,6,4,3])]
(0.52 secs, 32093988 bytes)

“R”和“U”列表:

*Main> let listRL = concatMap (\((a,b),c) -> b) applicationPath
*Main> listRL
["R","R","R","R","U","U","R","U","R","R","R","R","R","U","U","U","R","R","U","R"
..."U","R","R","R","R","U"]

使用起始模具和“R”和“U”列表的路径总和:

*Main> let sumPath path = foldr (\move (cost,die) -> (cost + dieTop (rollDie die move), rollDie die move)) (1,[4,3,1,6,2,5]) path
*Main> sumPath listRL
(1515,[5,2,1,6,4,3])

使用“R”和“U”列表从(r,c)计算(1,1)(因为我们从(1,1,)开始,(r,c)调整为(282-22, 199-24) }:

*Main> let rc path = foldr (\move (r,c) -> if move == "R" then (r,c+1) else (r+1,c)) (1,1) path
*Main> rc listRL 
(260,175)


算法/溶液

Continuing the research below, it seems that the minimal face-sum path (MFS) 
can be reduced, mod 4, by either rows or columns like so:

MFS (1,1) (r,c) == MFS (1,1) (r-4,c) + 14, for r > 7
                == MFS (1,1) (r,c-4) + 14, for c > 7

This makes finding the number for the minimal path straightforward:

MFS (1,1) (r,c) = 
  let numRowReductions = floor (r/4) - 1
      numColumnReductions = floor (c/4) - 1
      minimalR = r - 4 * numRowReductions
      minimalC = c - 4 * numColumnReductions
  in MFS (1,1) (minimalR,minimalC) + 14*numRowReductions + 14*numColumnReductions

minimalR and minimalC are always less than eight, which means we can easily
pre-calculate the minimal-face-sums for these and use that table to quickly
output the overall solution.

但我们如何找到路径呢?
从我的测试来看,似乎也有类似的结果:

MFS (1,1) (1,anything) = trivial
MFS (1,1) (anything,1) = trivial
MFS (1,1) (r,c), for r,c < 5 = calculate solution in your favorite way
MFS (1,1) (r,c), for either or both r,c > 4 = 
  MFS (1,1) (minimalR,minimalC) -> roll -> 
  MFS (1,1) (min 4 r-1, min 4 c-1) -> roll -> 
  ...sections must be arranged so the last one includes 
     four rotations for one axis and at least one for the other.
     keeping one row or column the same till the end seems to work.
     (For Paul's example above, after the initial MFS box, I moved in 
     fours along the x-axis, rolling 4x4 boxes to the right, which 
     means the y-axis advanced in threes and then a section in fours 
     going up, until the last box of 2x4. I suspect, but haven't checked, 
     that the sections must divide at least one axis only in fours for 
     this to work)...
  MFS (1,1) (either (if r > 4 then 4 else min 2 r, 4) 
             or     (4, if c > 4 then 4 else min 2 c))
  => (r,c) is now reached

例如,

  MFS (1,1) (5,13) = MFS (1,1) (1,5) -> roll right -> 
                     MFS (1,1) (1,4) -> roll right -> MFS (1,1) (5,4)

  MFS (1,1) (2,13) = MFS (1,1) (1,5) -> roll right -> 
                     MFS (1,1) (1,4) -> roll right -> MFS (1,1) (2,4)


经验测试中观察到的骰子属性

For target points farther than (1,1) to (2,3), for example (1,1) to (3,4)
or (1,1) to (4,6), the minimum path top-face-sum (MFS) is equal if you 
reverse the target (r,c). In other words: 

1. MFS (1,1) (r,c) == MFS (1,1) (c,r), for r,c > 2

不仅如此。

2. MFS (1,1) (r,c) == MFS (1,1) (r',c'), for r,c,r',c' > 2 and r + c == r' + c'
   e.g., MFS (1,1) (4,5) == MFS (1,1) (5,4) == MFS (1,1) (3,6) == MFS (1,1) (6,3)

但这里有趣的是:

The MFS for any target box (meaning from startPoint to endPoint) that
can be reduced to a symmetrical combination of (r,c) (r,c) or (r,c) (c,r), for 
r,c > 2, can be expressed as the sum of the MFS of the two smaller symmetrical 
parts, if the die-roll (the change in orientation) between the two parts is 
accounted for. In other words, if this is true, we can breakdown the calculation
into smaller parts, which is much much faster.

For example: 
 Target-box (1,1) to (7,6) can be expressed as: 
 (1,1) (4,3) -> roll right -> (1,1) (4,3) with a different starting orientation

 Check it, baby: 
 MFS  (1,1) (7,6) = MFS (1,1) (4,3) + MFS (1,1) (4,3) 
 (when accounting for the change in starting orientation, rolling right in 
 between)

 Eq. 2., implies that MFS (1,1) to (7,6) == MFS (1,1) (5,8)
 and MFS (1,1) (5,8) can be expressed as (1,1) (3,4) -> roll right -> (1,1) (3,4)

 Check it again: 
 MFS (1,1) (7,6) = MFS (1,1) (5,8) = MFS (1,1) (3,4) + MFS (1,1) (3,4)
 (when accounting for the change in starting orientation, rolling right in
 between)

不仅如此。

The symmetrical parts can apparently be combined in any way:

3. MFS (1,1) (r,c) -> roll-right -> MFS (1,1) (r,c) equals
   MFS (1,1) (r,c) -> roll-right -> MFS (1,1) (c,r) equals
   MFS (1,1) (r,c) -> roll-up -> MFS (1,1) (r,c) equals
   MFS (1,1) (r,c) -> roll-up -> MFS (1,1) (c,r) equals
   MFS (1,1) (2*r-1, 2*c) equals
   MFS (1,1) (2*r, 2*c-1), for r,c > 2

答案 4 :(得分:0)

<强>观

如果你必须在一条直线上移动,那么你所能做的最好就是用1和2结束你的动作,对于所有其他动作你不能做得比3.5*distance好。

<强>启发式:

使用ManhattanDistance = x + y可以使用以下启发式方法:

Heuristic = xH + yH;

,其中

xH = calculateStraightLineHeuristic(x)
yH = calculateStraightLineHeuristic(y)

并且函数calculateStraightLineHeuristic(z)定义如下:

calculateStraightLineHeuristic(z)
    if (z = 1)
        return zH = 1
    elseif (z = 2)
        return zH = 2+1
    else
        return zH = (z-2)*3.5+2+1
end