简体   繁体   English

如何在大空间尺度上加速 A* 算法?

[英]How to speed up A* algorithm at large spatial scales?

Fromhttp://ccl.northwestern.edu/netlogo/models/community/Astardemo , I coded an A* algorithm by using nodes in a network to define least-cost paths.http://ccl.northwestern.edu/netlogo/models/community/Astardemo 中,我通过使用网络中的节点来定义成本最低的路径,编写了一个 A* 算法。 The code seems to work but it is much too slow when I use it at large spatial scales.My landscape has an extent of 1000 patches x 1000 patches with 1 patch = 1 pixel.该代码似乎可以工作,但是当我在大空间尺度上使用它时速度太慢了。我的景观范围为 1000 个补丁 x 1000 个补丁,其中 1 个补丁 = 1 像素。 Even if I reduce it at 400 patches x 400 patches with 1 patch = 1 pixel, it is yet too slow (I can't modify my landscape below 400 patches x 400 patches).即使我将它减少到 400 个补丁 x 400 个补丁和 1 个补丁 = 1 像素,它仍然太慢(我无法修改低于 400 个补丁 x 400 个补丁的景观)。 Here is the code:这是代码:

to find-path [ source-node destination-node] 

let search-done? false
let search-path []
let current-node 0
set list-open []
set list-closed []  
let list-links-with-nodes-in-list-closed []
let list-links []

set list-open lput source-node list-open
while [ search-done? != true]
[    
ifelse length list-open != 0
[
  set list-open sort-by [[f] of ?1 < [f] of ?2] list-open 
  set current-node item 0 list-open 
  set list-open remove-item 0 list-open 
  set list-closed lput current-node list-closed
  ask current-node
  [  
    if parent-node != 0[
    set list-links-with-nodes-in-list-closed lput link-with parent-node list-links-with-nodes-in-list-closed 
    ]
    ifelse any? (nodes-on neighbors4) with [ (xcor = [ xcor ] of destination-node) and (ycor = [ycor] of destination-node)]
    [
      set search-done? true 
    ]
    [        
      ask (nodes-on neighbors4) with [ (not member? self list-closed) and (self != parent-node) ]  
      [  
        if not member? self list-open and self != source-node and self != destination-node
        [
          set list-open lput self list-open
          set parent-node current-node
          set list-links sentence (list-links-with-nodes-in-list-closed) (link-with parent-node)
          set g sum (map [ [link-cost] of ? ] list-links)
          set h distance destination-node 
          set f (g + h)
        ]
      ]
    ]
  ]
]

[
  user-message( "A path from the source to the destination does not exist." )
  report []
 ]
]
set search-path lput current-node search-path
let temp first search-path
while [ temp != source-node ]
[
 ask temp
[
  set color red
]
set search-path lput [parent-node] of temp search-path 
set temp [parent-node] of temp 
]
set search-path fput destination-node search-path
set search-path reverse search-path  
print search-path
end

Unfortunately, I don't know how to speed up this code.不幸的是,我不知道如何加速这段代码。 Is there a solution to calculate rapidly least-cost paths at large spatial scales ?是否有解决方案可以在大空间尺度上快速计算成本最低的路径?

Thanks very much for your help.非常感谢您的帮助。

Was curious so I tested mine A* and here is mine result很好奇所以我测试了我的 A*,这是我的结果

Maze 1280 x 800 x 32 bit pixels迷宫 1280 x 800 x 32 位像素

一个测试

  • as you can see it took ~23ms如您所见,耗时约 23 毫秒
  • no multithreading (AMD 3.2GHz)无多线程(AMD 3.2GHz)
  • C++ 32bit app (BDS2006 Turbo C++ or Borland C++ builder 2006 if you like) C++ 32 位应用程序(BDS2006 Turbo C++ 或 Borland C++ builder 2006,如果你喜欢)
  • the slowest path I found was ~44ms (fill almost whole map)我发现的最慢路径是 ~44ms(几乎填满整个地图)

I think that is fast enough ...我认为这已经足够快了......

Here is source for mine A* class:这是我的 A* 类的来源:

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
const DWORD A_star_space=0xFFFFFFFF;
const DWORD A_star_wall =0xFFFFFFFE;
//---------------------------------------------------------------------------
class A_star
    {
public:
    // variables
    DWORD **map;        // map[ys][xs]
    int xs,ys;          // map esolution   xs*ys<0xFFFFFFFE !!!
    int *px,*py,ps;     // output points px[ps],py[ps] after compute()

    // internals
    A_star();
    ~A_star();
    void _freemap();                                    // release map memory
    void _freepnt();                                    // release px,py memory

    // inteface
    void resize(int _xs,int _ys);                       // realloc map to new resolution
    void set(Graphics::TBitmap *bmp,DWORD col_wall);    // copy bitmap to map
    void get(Graphics::TBitmap *bmp);                   // draw map to bitmap for debuging
    void compute(int x0,int y0,int x1,int y1);          // compute path from x0,y0 to x1,y1 output to px,py
    };
//---------------------------------------------------------------------------
     A_star::A_star()   { map=NULL; xs=0; ys=0; px=NULL; py=NULL; ps=0; }
     A_star::~A_star()  { _freemap(); _freepnt(); }
void A_star::_freemap() { if (map) delete[] map; map=NULL; xs=0; ys=0; }
void A_star::_freepnt() { if (px) delete[] px; px=NULL; if (py) delete[] py; py=NULL; ps=0; }
//---------------------------------------------------------------------------
void A_star::resize(int _xs,int _ys)
    {
    if ((xs==_xs)&&(ys==_ys)) return;
    _freemap();
    xs=_xs; ys=_ys;
    map=new DWORD*[ys];
    for (int y=0;y<ys;y++)
     map[y]=new DWORD[xs];
    }
//---------------------------------------------------------------------------
void A_star::set(Graphics::TBitmap *bmp,DWORD col_wall)
    {
    int x,y;
    DWORD *p,c;
    resize(bmp->Width,bmp->Height);
    for (y=0;y<ys;y++)
     for (p=(DWORD*)bmp->ScanLine[y],x=0;x<xs;x++)
        {
        c=A_star_space;
        if (p[x]==col_wall) c=A_star_wall;
        map[y][x]=c;
        }
    }
//---------------------------------------------------------------------------
void A_star::get(Graphics::TBitmap *bmp)
    {
    int x,y;
    DWORD *p,c;
    bmp->SetSize(xs,ys);
    for (y=0;y<ys;y++)
     for (p=(DWORD*)bmp->ScanLine[y],x=0;x<xs;x++)
        {
        c=map[y][x];
             if (c==A_star_wall ) c=0x00000000;
        else if (c==A_star_space) c=0x00FFFFFF;
        else                      c=((c>>1)&0x7F)+0x00404040;
        p[x]=c;
        }
    }
//---------------------------------------------------------------------------
void A_star::compute(int x0,int y0,int x1,int y1)
    {
    int x,y,xmin,xmax,ymin,ymax,xx,yy;
    DWORD i,j,e;
    // [clear previous paths]
    for (y=0;y<ys;y++)
     for (x=0;x<xs;x++)
      if (map[y][x]!=A_star_wall)
       map[y][x]=A_star_space;
/*
    // [A* no-optimizatims]
    xmin=x0; xmax=x0; ymin=y0; ymax=y0;
    if (map[y0][x0]==A_star_space)
     for (i=0,j=1,e=1,map[y0][x0]=i;(e)&&(map[y1][x1]==A_star_space);i++,j++)
      for (e=0,y=ymin;y<=ymax;y++)
       for (   x=xmin;x<=xmax;x++)
        if (map[y][x]==i)
        {
        yy=y-1; xx=x; if ((yy>=0)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; if (ymin>yy) ymin=yy; }
        yy=y+1; xx=x; if ((yy<ys)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; if (ymax<yy) ymax=yy; }
        yy=y; xx=x-1; if ((xx>=0)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; if (xmin>xx) xmin=xx; }
        yy=y; xx=x+1; if ((xx<xs)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; if (xmax<xx) xmax=xx; }
        }
*/
    // [A* changed points list]
    // init space for 2 points list
    _freepnt();
    int i0=0,i1=xs*ys,n0=0,n1=0,ii;
    px=new int[i1*2];
    py=new int[i1*2];
    // if start is not on space then stop
    if (map[y0][x0]==A_star_space)
        {
        // init start position to first point list
        px[i0+n0]=x0; py[i0+n0]=y0; n0++; map[y0][x0]=0;
        // search until hit the destination (swap point lists after each iteration and clear the second one)
        for (j=1,e=1;(e)&&(map[y1][x1]==A_star_space);j++,ii=i0,i0=i1,i1=ii,n0=n1,n1=0)
         // test neibours of all points in first list and add valid new points to second one
         for (e=0,ii=i0;ii<i0+n0;ii++)
            {
            x=px[ii]; y=py[ii];
            yy=y-1; xx=x; if ((yy>=0)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; px[i1+n1]=xx; py[i1+n1]=yy; n1++; map[yy][xx]=j; }
            yy=y+1; xx=x; if ((yy<ys)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; px[i1+n1]=xx; py[i1+n1]=yy; n1++; map[yy][xx]=j; }
            yy=y; xx=x-1; if ((xx>=0)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; px[i1+n1]=xx; py[i1+n1]=yy; n1++; map[yy][xx]=j; }
            yy=y; xx=x+1; if ((xx<xs)&&(map[yy][xx]==A_star_space)){ map[yy][xx]=j; e=1; px[i1+n1]=xx; py[i1+n1]=yy; n1++; map[yy][xx]=j; }
            }
        }
    // [reconstruct path]
    _freepnt();
    if (map[y1][x1]==A_star_space) return;
    if (map[y1][x1]==A_star_wall) return;
    ps=map[y1][x1]+1;
    px=new int[ps];
    py=new int[ps];
    for (i=0;i<ps;i++) { px[i]=x0; py[i]=y0; }
    for (x=x1,y=y1,i=ps-1,j=i-1;i>=0;i--,j--)
        {
        px[i]=x;
        py[i]=y;
        if ((y>   0)&&(map[y-1][x]==j)) { y--; continue; }
        if ((y<ys-1)&&(map[y+1][x]==j)) { y++; continue; }
        if ((x>   1)&&(map[y][x-1]==j)) { x--; continue; }
        if ((x<xs-0)&&(map[y][x+1]==j)) { x++; continue; }
        break;
        }
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

I know it is a bit too much code but it is complete.我知道代码有点多,但它是完整的。 The important stuff is in member function compute so search for [A* changed points list] .重要的是在成员函数compute因此搜索[A* changed points list] The unoptimized A* (rem-ed) is about 100 times slower.未优化的A* (rem-ed)大约慢 100 倍。

Code use bitmap from Borland VCL so if you do not have it ignore functions get,set and rewrite them to your input/output gfx style.代码使用来自 Borland VCL 的位图,因此如果您没有它,则忽略函数get,set并将它们重写为您的输入/输出 gfx 样式。 They just load map from bitmap and draw computed map back to bitmap他们只是从bitmap加载map并将计算出的mapbitmap

Usage:用法:

// init
A_star map;
Graphics::TBitmap *maze=new Graphics::TBitmap;
maze->LoadFromFile("maze.bmp");
maze->HandleType=bmDIB;
maze->PixelFormat=pf32bit;
map.set(maze,0); // walls are 0x00000000 (black)
// this can be called repetitive without another init
map.compute(x0,y0,x1,y1); // map.px[map.ps],map.py[map.ps] holds the path
map.get(maze,0); // this is just for drawing the result map back to bitmap for viewing

for more info about A* see Backtracking in A star有关 A* 的更多信息,请参阅A 星中的回溯

A* is two heuristics; A* 是两个启发式; Dijkstra's Algorithm & Greedy Search. Dijkstra 的算法和贪婪搜索。 Dijkstra's Algorithm searches for the shortest path. Dijkstra 算法搜索最短路径。 The Greedy Search looks for the cheapest path.贪婪搜索寻找最便宜的路径。 Dijkstra's algorithm is extraordinarily slow because it doesn't take risks. Dijkstra 的算法非常慢,因为它不冒险。 Multiply the effect of the Greedy Search to take more risks.乘以贪婪搜索的效果以承担更多风险。

For example, if A* = Dijkstra + Greedy , then a faster A* = Dijkstra + 1.1 * Greedy .例如,如果A* = Dijkstra + Greedy ,那么更快的A* = Dijkstra + 1.1 * Greedy No matter how much you optimize your memory access or your code, it will not fix a bad approach to solving the problem.无论您如何优化内存访问或代码,它都不会修复解决问题的糟糕方法。 Make your A* more greedy and it will focus on finding a solution, rather than a perfect solution.让你的 A* 更加贪婪,它会专注于寻找解决方案,而不是完美的解决方案。

NOTE:笔记:

Greedy Search = distance from end
Dijkstra's Algorithm = distance from start

in standard A*, it will seek perfect solutions until reaching an obstacle.在标准 A* 中,它将寻求完美的解决方案,直到遇到障碍。 This video shows the different search heuristics in action;该视频展示了不同的搜索启发式方法; notice how fast a greedy search can be (skip to 2:22 for A*, 4:40 for Greedy).注意贪婪搜索的速度有多快(A* 跳到 2:22,贪婪跳到 4:40)。 I myself had a similar issue when I first began with A* and the modified A* I outline above improved my performance exponentially .当我第一次开始使用 A* 时,我自己也遇到了类似的问题,上面概述的修改后的 A* 以指数方式提高了我的性能。 Moral of the story;故事的道德启示; use the right tool for the job.为工作使用正确的工具。

TL;DR: Include in your node list (graph) only the patches (or agents) that are important! TL;DR:在您的节点列表(图表)中仅包含重要的补丁(或代理)!

One way to speed things up is to not search over every grid space.加快速度的一种方法是不搜索每个网格空间。 A* is a graph search, but seems like most coders just dump every point in the grid into the graph. A* 是一种图形搜索,但似乎大多数编码人员只是将网格中的每个点都转储到图形中。 That's not required.那不是必需的。 Using a sparse search graph, rather than searching every point on the screen, can speed things up.使用稀疏搜索图,而不是搜索屏幕上的每个点,可以加快速度。

Even in a complex maze, you can speed up by only including corners and junctions in the graph.即使在复杂的迷宫中,您也可以通过在图形中仅包含角落和交叉点来加快速度。 Don't add hallway grids to the open list--seek ahead to find the next corner or junction.不要将走廊网格添加到开放列表中——提前寻找下一个角落或路口。 This is where pre-processing the screen/grid/map to construct the search graph can save time later.这是预处理屏幕/网格/地图以构建搜索图可以节省时间的地方。

As you can see in this image from my (rather inefficient) A* model on turtlezero.com, a naive approach creates a lot of extra steps.正如您从我在turtlezero.com 上的(相当低效的)A* 模型中看到的那样,一种幼稚的方法会产生许多额外的步骤。 Any open nodes created in a long straight corridor are wasted:在长而直的走廊中创建的任何开放节点都被浪费了:

用naive a-star解决的样本迷宫

By eliminating these steps from the graph, the solution could be found hundreds of times faster.通过从图中消除这些步骤,可以以数百倍的速度找到解决方案。

Another sparse graph technique is to use a graph that is gradually less dense the further from the walker.另一种稀疏图技术是使用离步行者越远密度逐渐降低的图。 That is, make your search space detailed near the walker, and sparse (fewer nodes, less accurate regarding obstacles) away from the walker.也就是说,使您的搜索空间靠近步行者,并远离步行者稀疏(节点较少,关于障碍物的准确性较低)。 This is especially useful where the walker is moving through detailed terrain on a map that is changing or towards a target that is moving and the route has to be recalculated anyway.当步行者穿过正在变化的地图上的详细地形或朝向正在移动的目标并且无论如何都必须重新计算路线时,这尤其有用。

For example, in a traffic simulation where roads may become gridlocked, or accidents occur.例如,在道路可能会堵塞或发生事故的交通模拟中。 Likewise, a simulation where one agent is pursuing another agent on a changing landscape.同样,一个代理在不断变化的环境中追逐另一个代理的模拟。 In these cases, only the next few steps need to be exactly plotted.在这些情况下,只需要精确绘制接下来的几个步骤。 The general route to the destination can be approximate.到达目的地的一般路线可以是近似的。

One simple way to implement this is to gradually increase the step size of the walker as the path becomes longer.实现这一点的一种简单方法是随着路径变长逐渐增加步行者的步长。 Disregard obstacles or do a quick line-intersection or tangent test.忽略障碍物或进行快速线交叉或切线测试。 This gives the walker a general idea of where to go.这让步行者对去哪里有一个大致的了解。

An improved path can be recalculated with each step, or periodically, or when an obstacle is encountered.改进后的路径可以在每一步重新计算,或者定期重新计算,或者在遇到障碍物时重新计算。

It may only be milliseconds saved, but milliseconds wasted on the soon-to-change end of the path could be better used providing brains for more walkers, or better graphics, or more time with your family.它可能只节省了几毫秒,但可以更好地利用在即将改变的路径末端浪费的几毫秒,为更多步行者提供大脑,或更好的图形,或更多时间与家人在一起。

For an example of a sparse graph of varying density, see chapter 8 of Advanced Java Programming By David Wallace Croft from APress: http://www.apress.com/game-programming/java/9781590591239有关不同密度的稀疏图的示例,请参阅 APress 的 David Wallace Croft 所著的 Advanced Java Programming 的第 8 章: http : //www.apress.com/game-programming/java/9781590591239

He uses a circular graph of increasing sparseness in a demo tank game with an a* algorithm driving the enemy tanks.他在演示坦克游戏中使用了渐增稀疏的圆形图,并使用 a* 算法驱动敌方坦克。

Another sparse graph approach is to populate the graph with only way-points of interest.另一种稀疏图方法是仅用感兴趣的路径点填充图。 For example, to plot a route across a simple campus of buildings, only entrances, exits, and corners are important.例如,要绘制穿过简单建筑物校园的路线,只有入口、出口和角落是重要的。 Points along the side of a building or in the open space between are not important, and can be omitted from the search graph.建筑物侧面或之间空地中的点并不重要,可以从搜索图中省略。 A more detailed map might need more way-points--such as a circle of nodes around a fountain or statue, or where paved paths intersect.更详细的地图可能需要更多的路径点——例如围绕喷泉或雕像的节点圈,或者铺好的路径相交的地方。

Here's a diagram showing the paths between waypoints.这是一个图表,显示了航点之间的路径。

用于路径搜索优化的构建拐角航点示例

This was generated by the campus-buildings-path-graph model by me on turtlezero.com: http://www.turtlezero.com/models/view.php?model=campus-buildings-path-graph这是由我在turtlezero.com上的校园建筑路径图模型生成的: http ://www.turtlezero.com/models/view.php?model=campus-buildings-path-graph

It uses simple netlogo patch queries to find points of interest, like outside and inside corners.它使用简单的 netlogo 补丁查询来查找兴趣点,例如外角和内角。 I'm sure a somewhat more sophisticated set of queries could deal with things like diagonal walls.我确信一组更复杂的查询可以处理对角墙之类的问题。 But even without such fancy further optimization, the A* search space would be reduced by orders of magnitude.但即使没有这种花哨的进一步优化,A* 搜索空间也会减少几个数量级。

Unfortunately, since now Java 1.7 won't allow unsigned applets, you can't run the model in the webpage without tweaking your java security settings.不幸的是,由于现在 Java 1.7 不允许未签名的小程序,因此您无法在网页中运行模型而不调整 Java 安全设置。 Sorry about that.对于那个很抱歉。 But read the description.但请阅读说明。

If you plan to reuse the same map multiple times, some form of pre-processing is usually optimal.如果您计划多次重用同一个地图,某种形式的预处理通常是最佳的。 Effectively, you work out the shortest distances between some common points, and add them to the graphs as edges, this will typically help a* find a solution more quickly.有效地,您计算出一些公共点之间的最短距离,并将它们作为边添加到图中,这通常有助于 a* 更快地找到解决方案。 Although its more difficult to implement.虽然实施起来比较困难。

Eg you might do this for all motorway routes in a map of the UK, so the search algorithm only has to find a route to a motorway, and from the motorway junctions to its destination.例如,您可以对英国地图中的所有高速公路路线执行此操作,因此搜索算法只需找到一条通往高速公路的路线,以及从高速公路路口到其目的地的路线。

I can't tell what the actual cause of the observed slowlyness might be.我不知道观察到的缓慢的实际原因可能是什么。 Maybe it's just due to shortcomings in efficiency imposed by the programming language at hand.也许这只是由于手头的编程语言在效率上存在缺陷。 How did you measure your performance?你是如何衡量你的表现的? How can we reproduce it?我们如何重现它?

Besides that, the heuristic (distance metric) being used has a large influence on the amount of exploration that is done in order to find the optimal path and thus also influences the perceived efficiency of the algorithm.除此之外,所使用的启发式(距离度量)对为找到最佳路径而进行的探索量有很大影响,因此也会影响算法的感知效率。

In theory you have to use an admissible heuristic, that is, one that never overestimates the remaining distance.从理论上讲,您必须使用一种可接受的启发式方法,即永远不会高估剩余距离的方法。 In practice, depending on the complicatedness of the maze, a conservative choice for a 2d grid maze, like manhattan distance, might significantly underestimate the remaining distance.在实践中,根据迷宫的复杂性,对于 2d 网格迷宫的保守选择,如曼哈顿距离,可能会大大低估剩余距离。 Therefore a lot of exploration is done in areas of the maze far away from the goal.因此,在远离目标的迷宫区域进行了大量探索。 This leads to a degree of exploration that resembles much rather that of an exhaustive search (eg, Breadth-First Search), than what one would expect from an informed search algorithm.这会导致一定程度的探索,而不是类似于详尽搜索(例如,广度优先搜索),而不是人们对知情搜索算法的期望。

This might be something to look into.这可能是值得研究的事情。

Also have a look at my related answer here:还可以在这里查看我的相关答案:

There I have compared different heuristics used with the basic A-Star algorithm and visualized the results.在那里,我比较了与基本 A-Star 算法一起使用的不同启发式方法,并将结果可视化。 You might find it interesting.你可能会觉得很有趣。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM