多单位寻路算法,寻找最优解

对于单个单位的寻路可以使用A*算法。但是在实际应用中往往出现多个单位同时移动的场面,而且它们会互相影响,阻碍对方的移动。所以一旦冲突,之前为每个单位计算出的路径就会失效。

一种流行的解决方法是发现冲突的时候重新计算路径。还有定期重新计算的等等。这些都是动态调整的方案,最后形成的路径并非是最优的。虽然这些次优解可以满足大部分场景的需要,但是有没有办法计算出最优解呢?毕竟很多动态调整的算法会有失败的可能。

我们可以把一个最小时间单位(回合)中每个单位的可能移动的组合列出来作为节点,放入搜索树中,然后再用A*算法进行搜索。但是在方格地图中一个单位可以有最多5或者9(带斜线)种不同的移动方法,其中包括静止不动。2个单位就有5x5或9x9种组合。随着要考虑的单位数的增加,组合数会爆炸。这时我们可以把一个回合再分解到一系列单个单位的移动,把单个移动作为节点。这样一旦发现某些条件不满足,比如两个单位有冲突,就可以关闭当前节点,不继续生成组合,从而实现对搜索树进行剪枝。由于一个回合中各单位的移动实际并没有优先次序,而我们现在的处理是一个单位一个单位进行,所以先被处理的单位不需要检查和后面单位的冲突,只有后面的单位需要检查和前面单位的冲突。当然为了成功剪枝,优先处理哪些单位也是值得考虑的。可以按某种优先级排序,比如越接近目标的可以优先处理。

举个例子,假定一个2x2的地图,有两个单位A在坐标1,0处,B在坐标0,1处,都想要移动到坐标1,1处。那么我们按先处理A后处理B的次序生成节点。假定[]表示计划的移动列表,{}表示未计划的单位列表。那么搜索树可以表示如下:

                                                []{A10,B01} //初始状态

                                                                    /                                                    

                                                              [A向下]{B01}                         [A不动]{B01}未展开       [A向左]{B01}未展开

                                   /                     |       

              [A向下B向右]{}冲突,剪枝      [A向下B不动]{}             [A向下B向上]{} 未展开

                                                     |

                                                    []{A11,B01}     ->     []{B01} 回合1结束,结算位置,A到达目的地,被移走,然后进行下一轮。

                                                                          /                                      

                                                                            [B向右]{}      [B不动]{}未展开    [B向上]{}未展开

                                                                     |

                                                                             []{} 回合2结束,结算位置,B到达目的地,被移走。列表为空,达成目标状态。

另一个优化是,虽然我们可以生成一个单位的所有移动方法,但是我们并不急于把所有的移动都作为节点放入open list中,而只选1~2个最有可能成功的。当前节点并不关闭,并且维护一个列表,记录有哪些移动已经展开过了。这样可以有效减少open list中节点的数量。假定<>为未展开的移动,上面的例子可以用如下表示:

                                                                        []{A10,B01} //初始状态    -> []<A不动, A向左>{}

                                                                           /                

                                                            [A向下]{B01}  ->    [A向下]<B向上>{} 

                                              /                     |       

                                 [A向下B向右]{}冲突,剪枝      [A向下B不动]{}            

                                                                     |

                                                                      []{A11,B01}     ->     []{B01}  ->   []<B不动,B向上> 回合1结束,结算位置,A到达目的地,被移走,然后进行下一轮。

                                                                                                      |        

                                                                                                          [B向右]{}     

                                                                                                      |

                                                                                                                []{} 回合2结束,结算位置,B到达目的地,被移走。列表为空,达成目标状态。

关于A*搜索的启发函数,我们当然可以求出每个单位的manhattan距离、diagonal距离或者duclidean距离,然后求和作为每个节点的启发值。更好的启发值可以用RRA*(Reverse Resumable A*)算法来获得。也就对目标点到当前点执行一次普通A*搜索,来获得(不计移动单位的障碍)的实际距离。这个距离比前面提到的估算算法要准确得多。RRA*搜索生成的open list是可以重用的,实际上开销并不大。当然对多个目标点,我们需要维护各自的open list。

具体实现可以参考github.com/silmerusse/fudge_pathfinding中的例子。对于相对简单的地图和较少的单位,最优解是可以很快求出的。但是对复杂度高的地图和较多单位,则需要花费较多时间和内存空间。

原文地址:https://www.cnblogs.com/silmerusse/p/4533959.html