A星算法

A星算法

    搜寻算法俗称A星算法。这是一种在图形平面有多个节点的路径上,求出由起点到目标点的最小路径耗费算法,主要搜寻路径的方式为启发推进式。常用于游戏中的NPC(Non-Player-Controlled Character 即“非人控制玩家角色)的移动计算,或线上游戏的BOT的移动计算。

    此种寻找最佳路径的算法类似于图论中寻找最优树,通常是通盘考虑选择某一路径的路径耗费,在所有可通过的路径中总有一条路径相对于其他任何可通行的路径来说是耗费最少的。在图论中寻找最佳路径时每一条的路径耗费是已知且固定的,但在用A星算法求解最佳路径时,沿着不同的路径前进,尽管是同一节点但其耗费可能缺是不同的,这便是是启发式寻路的精髓。

    运用此方式时,首先将实际问题抽象出来,用矩阵的形式表示问题中的各元素,包括起点位置,目标点位置,以及出现的障碍物。我们会逐渐地发现在寻路方面都是将实际问题抽象地用矩阵表示,之后通过对矩形的操作模拟实现寻路过程。

    其基本思想是以起点为中心,其周围紧邻的8个点都通过指针指向它,在其周围点内选择最佳路径点并以它为中心点将其还未建立指针联系的周围点(可行的,这在后文中解释)通过指针指向它,并选择最佳路径点再以此点为中心寻路直到寻找到点的周围点中有目标点,这样寻找的路径就通过指针一一连接起来了,最后通过输出这些点就是寻找的路径了。(ps:在寻找最佳路径时可能会改变部分点的指针指向)

本文主要通过以下几个方面来逐步分析A星算法的寻路过程:

1、将实际问题抽象化为矩阵表示

    抽象出的矩阵如上图所示(来源已在文章末尾标注清楚),其中绿色区域表示起始点,红色区域表示目标点,中间蓝色区域表示障碍物(如不可通过的高山或是河流),黑色区域表示可产生路径的区域。

2、以起点为中心寻找到下一节点

    如上图所示,以起点为中心与之紧密相邻的8个点是其所寻路径上可到的下一点,且都以指针的形式将中间当前点作为与其紧邻的周围点的父节点。对于这8个点,应该选择哪一点作为寻路的下一个起点呢,A星算法中建立了两个列表,一个为开启列表(用于存储所有当前点的可到点(除去已经在关闭列表中的点、障碍物点)),另一个为关闭列表(里面存储已经到过的点,已经在关闭列表中的点在下一次寻路的过程中是不会再次检查的,这也说明寻路的线路不会有相交的可能)。

3、 选择下一节点

    将起点加入关闭列表,在以后的寻路过程中不再对其进行检查,接下来就是在这8个点中选择一个作为下一路径点,选择的原则是在其中寻找路径耗费最小的节点,

其权值用F表示,F=G+H

其中G表示从起点开始,沿着产生的路径,移动到指定方格上的路径耗费;如下图所示,以起点为中心其紧邻周围点有上下左右、对角线方向上的8个点,以上下左右移动路径耗费为10,对角线耗费为√2*10,约为14。

其中H表示从路径所在的当前点到终点的移动路径耗费,计算方法为当前点到目的点之间水平和垂直的方格的数量总和,然后把结果乘以10。

 从上图可以看出,起始点右边点的权值F最小,故将其作为下一路径点。

4、继续搜索

    把路径点从开启列表中删除,并添加到关闭列表中。检查与此点紧邻的8个点(忽略在关闭列表中或者不可通过的点),把他们添加进开启列表,如果存在还有点没有添加进开启列表,则将路径点作为此类点的父节点并添加进开启列表。
    如果所有可行的紧邻点已经在开启列表中,对每一紧邻点检查目前这条路径到是否比上一路径点到这一紧邻点的路径耗费要小,如果不是则什么都不用做(如下图所示)从原始起点到其紧邻的右下方的点,按照新产生路径G值:G1=10+10=20,而原始路径G值G2=14,即新产生路径的G值比原始路径的G值大,而它们的H值相同(为同一点)故原始路径的F值比新产生路径的F值要小,不做任何处理,继续下一步寻路。如果是,那就把相邻方格的父节点改为目前选中的方格,说明新产生的路径的移动耗费更小。

   5、重复上一搜索过程直至结束

    搜寻过程结束分为两种情况:一种是目标点加入关闭列表,搜索正常结束,找到路径!另一种情况是目标点未找到但开启列表已经为空,意味着没有找到从起始点到目标点的路径,搜索结束。

    搜索过程如下图所示,从中可以看出从起点到目标点之间有指针指向一致的一条路径,这便A星算法是搜寻到的路径。

 

     在路径点上添加红色点突出显示(如下图所示),此即为从起始点到终点的一条路径

 整个寻路过程整理如下:

1、起始格加入开启列表;
2、重复如下的工作:
  a) 寻找开启列表中F值最低的点。我们称它为当前点;
  b) 把它加入关闭列表;
  c) 对紧邻的8格中的每一点
     * 如果它不可通过或者已经在关闭列表中,略过它。反之如下:
     * 如果它不在开启列表中,把它添加进去。把当前点作为这一格的父节点。记录这一点的F、G和H值;
     * 如果它已经在开启列表中,用G值为参考检查新的路径是否更好。更低的G值意味着更好的路径。如果是这样,就把这一点的父节 点改成当前点,并且重新计算这一点的G和F值。改变之后需要重新对开启列表按F值大小排序。如果不是则不需要做后面改变指针指向并重新计算G、F值的工作;

3、停止搜索,分为两种情况:
          * 当目标点添加入了关闭列表,
这时候路径被找到,搜索正常结束;
          * 没有找到目标点,但开启列表已经空了,此时未找到合适的路径,搜索结束;
4、保存路径。从目标点开始,沿着每一点的指针指向移动,直到回到起始点,输出路径。

 

 具体算法实现(此处用C语言实现):

  1     #include <stdio.h>  
  2 
  3     #define STARTNODE   1  //起始点
  4     #define ENDNODE     2  //终点
  5     #define BARRIER     3  //障碍物
  6       
  7     typedef struct AStarNode  //定义一个具有A星节点特点的结构体
  8     {  
  9         int s_x;    // 坐标
 10         int s_y;  
 11         int s_g;    // 起点到此点的路径耗费( 由g和h可以得到f,此处f省略,f=g+h )  
 12         int s_h;      // 启发函数预测的当前点到终点的路径耗费 
 13         int s_style;   // 结点类型:1表示起始点,2表示目标点,3表示障碍物(如不可通过的高山或是河流),0表示可产生路径的区域  
 14         struct AStarNode * s_parent;    //指向父节点的指针  
 15         int s_is_in_closetable;     // 是否在close表中  
 16         int s_is_in_opentable;      // 是否在open表中  
 17     }AStarNode, *pAStarNode;  
 18       
 19     AStarNode  map_maze[6][8];        // 结点数组  
 20     pAStarNode open_table[100];     // open表  
 21     pAStarNode close_table[100];        // close表  
 22     int   open_node_count;    // open表中节点数量  
 23     int    close_node_count;    // close表中结点数量  
 24     pAStarNode path_stack[100];     // 保存路径的栈  
 25     int        top = -1;            // 栈顶  
 26           
 27      // 交换两个元素  
 28 
 29     void swap( int idx1, int idx2 )    
 30     {    
 31         pAStarNode tmp = open_table[idx1];  
 32         open_table[idx1] = open_table[idx2];  
 33         open_table[idx2] = tmp;  
 34     }    
 35       
 36     // 堆调整,此处用到小顶堆的算法,对开启列表进行堆调整后其第一个元素必是该列表中值最小的元素
 37 
 38     ///////////////////////////////////////////////////////////////////////////
 39     //////////////////////////////////////////////////////////////////////////
 40     //   
 41     void adjust_heap( int nIndex )      
 42     {     
 43         int curr = nIndex;  
 44         int child = curr * 2 + 1;   // 得到左孩子idx( 下标从0开始,所以左孩子是curr*2+1 )  
 45         int parent = ( curr - 1 ) / 2;  // 得到双亲idx  
 46       
 47         if (nIndex < 0 || nIndex >= open_node_count)  
 48         {  
 49             return;  
 50         }  
 51           
 52         // 往下调整( 要比较左右孩子和cuur parent )  
 53         //   
 54         while ( child < open_node_count )  
 55         {  
 56             // 小根堆是双亲值小于孩子值  
 57             //   
 58             if ( child + 1 < open_node_count && open_table[child]->s_g + open_table[child]->s_h  > open_table[child+1]->s_g + open_table[child+1]->s_h )  
 59             {  
 60                 ++child;// 判断左右孩子大小  
 61             }  
 62               
 63             if (open_table[curr]->s_g + open_table[curr]->s_h <= open_table[child]->s_g + open_table[child]->s_h)  
 64             {  
 65                 break;  
 66             }  
 67             else  
 68             {  
 69                 swap( child, curr );            // 交换节点  
 70                 curr = child;               // 再判断当前孩子节点  
 71                 child = curr * 2 + 1;           // 再判断左孩子  
 72             }  
 73         }  
 74           
 75         if (curr != nIndex)  
 76         {  
 77             return;  
 78         }  
 79       
 80         // 往上调整( 只需要比较cuur child和parent )  
 81         //   
 82         while (curr != 0)  
 83         {  
 84             if (open_table[curr]->s_g + open_table[curr]->s_h >= open_table[parent]->s_g + open_table[parent]->s_h)  
 85             {  
 86                 break;  
 87             }  
 88             else  
 89             {  
 90                 swap( curr, parent );  
 91                 curr = parent;  
 92                 parent = (curr-1)/2;  
 93             }  
 94         }  
 95     }  
 96     //////////////////////////////////////////////////////////////////////////
 97     //////////////////////////////////////////////////////////////////////////
 98       
 99     // 判断邻居点是否可以进入open表  
100     //   
101     void insert_to_opentable( int x, int y, pAStarNode curr_node, pAStarNode end_node, int w )  
102     {  
103         int i;  
104       
105         if ( map_maze[x][y].s_style != BARRIER )        // 不是障碍物  
106         {  
107             if ( !map_maze[x][y].s_is_in_closetable )   // 不在闭表中  
108             {  
109                 if ( map_maze[x][y].s_is_in_opentable ) // 在open表中  
110                 {  
111                     // 需要判断是否是一条更优化的路径  
112                     //   
113                     if ( map_maze[x][y].s_g > curr_node->s_g + w )    // 如果更优化  
114                     {  
115                         map_maze[x][y].s_g = curr_node->s_g + w;  
116                         map_maze[x][y].s_parent = curr_node;          //此处将中心点与其周围8个点通过指针连接起来
117       
118                         for ( i = 0; i < open_node_count; ++i )  
119                         {  
120                             if ( open_table[i]->s_x == map_maze[x][y].s_x && open_table[i]->s_y == map_maze[x][y].s_y )  
121                             {  
122                                 break;  
123                             }  
124                         }  
125       
126                         adjust_heap( i );                   // 下面调整点  
127                     }  
128                 }  
129                 else                                    // 不在open中  
130                 {  
131                     map_maze[x][y].s_g = curr_node->s_g + w;  
132                     map_maze[x][y].s_h = abs(end_node->s_x - x ) + abs(end_node->s_y - y);  
133                     map_maze[x][y].s_parent = curr_node;  
134                     map_maze[x][y].s_is_in_opentable = 1;  
135                     open_table[open_node_count++] = &(map_maze[x][y]);  
136                 }  
137             }  
138         }  
139     }  
140       
141     // 查找邻居  
142     // 对上下左右8个邻居进行查找  
143     //    
144     void get_neighbors( pAStarNode curr_node, pAStarNode end_node )  
145     {  
146         int x = curr_node->s_x;  
147         int y = curr_node->s_y;  
148       
149         // 下面对于8个邻居进行处理!  
150         //   
151        if((x+1)>=0&&(x+1)<6&&y>=0&&y<8)
152     {
153     insert_to_opentable(x+1,y,curr_node,end_node,10);
154     }
155 
156     if((x-1)>=0&&(x-1)<6&&y>=0&&y<8)
157     {
158     insert_to_opentable(x-1,y,curr_node,end_node,10);
159     }
160     
161     if(x>=0&&x<6&&(y+1)>=0&&(y+1)<8)
162     {
163     insert_to_opentable(x,y+1,curr_node,end_node,10);
164     }
165 
166     if(x>=0&&x<6&&(y-1)>=0&&(y-1)<8)
167     {
168     insert_to_opentable(x,y-1,curr_node,end_node,10);
169     }
170 
171     if((x+1)>=0&&(x+1)<6&&(y+1)>=0&&(y+1)<8)
172     {
173     insert_to_opentable(x+1,y+1,curr_node,end_node,14);
174     }
175 
176     if((x+1)>=0&&(x+1)<6&&(y-1)>=0&&(y-1)<8)
177     {
178     insert_to_opentable(x+1,y-1,curr_node,end_node,14);
179     }
180 
181     if((x-1)>=0&&(x-1)<6&&(y+1)>=0&&(y+1)<8)
182     {
183     insert_to_opentable(x-1,y+1,curr_node,end_node,14);
184     }
185 
186     if((x-1)>=0&&(x-1)<6&&(y-1)>=0&&(y-1)<8)
187     {
188     insert_to_opentable(x-1,y-1,curr_node,end_node,14); 
189         }  
190     }  
191          
192     int main()  
193     {   
194         // 地图数组的定义  
195         //   
196         AStarNode *start_node;          // 起始点  
197         AStarNode *end_node;            // 结束点  
198         AStarNode *curr_node;           // 当前点  
199         int       is_found;         // 是否找到路径  
200         int maze[6][8] ={           // 仅仅为了好赋值给map_maze  
201                             {0,0,0,0,0,0,0,0},  
202                             {0,0,0,0,3,0,0,0},               
203                             {0,0,1,0,3,0,2,0},  
204                             {0,0,0,0,3,0,0,0},  
205                             {0,0,0,0,0,0,0,0},  
206                             {0,0,0,0,0,0,0,0},  
207                         };  
208         int       i,j,x;  
209           
210         // 下面准备点  
211         //   
212         for( i = 0; i < 6; ++i )  
213         {  
214             for ( j = 0; j < 8; ++j )  
215             {  
216                 map_maze[i][j].s_g = 0;  
217                 map_maze[i][j].s_h = 0;  
218                 map_maze[i][j].s_is_in_closetable = 0;  
219                 map_maze[i][j].s_is_in_opentable = 0;  
220                 map_maze[i][j].s_style = maze[i][j];  
221                 map_maze[i][j].s_x = i;  
222                 map_maze[i][j].s_y = j;  
223                 map_maze[i][j].s_parent = NULL;  
224       
225                 if ( map_maze[i][j].s_style == STARTNODE )  // 起点  
226                 {  
227                     start_node = &(map_maze[i][j]);  
228                 }  
229                 else if( map_maze[i][j].s_style == ENDNODE )    // 终点  
230                 {  
231                     end_node = &(map_maze[i][j]);  
232                 }  
233       
234                 printf("%d ", maze[i][j]);  
235             }  
236       
237             printf("\n");  
238         }  
239       
240         // 下面使用A*算法得到路径  
241         //    
242         open_table[open_node_count++] = start_node;         // 起始点加入open表  
243           
244         start_node->s_is_in_opentable = 1;               // 加入open表  
245         start_node->s_g = 0;  
246         start_node->s_h = abs(end_node->s_x - start_node->s_x) + abs(end_node->s_y - start_node->s_y);  
247         start_node->s_parent = NULL;  
248           
249         if ( start_node->s_x == end_node->s_x && start_node->s_y == end_node->s_y )  
250         {  
251             printf("起点==终点!\n");  
252             return 0;  
253         }  
254           
255         is_found = 0;  
256       
257         while( 1 )  
258         {  
259            
260            curr_node = open_table[0];      // open表的第一个点一定是f值最小的点(通过堆排序得到的)  
261             open_table[0] = open_table[--open_node_count];  // 最后一个点放到第一个点,然后进行堆调整,
262             //即将最小F值的点选择为路径点后就将其从开启列表中删除(此处是用列表中最后一个点来覆盖,
263             //节省了很多时间,省去了对元素的移位操作)  
264 
265             adjust_heap( 0 );               // 调整堆  
266               
267             close_table[close_node_count++] = curr_node;    // 当前点加入close表  
268             curr_node->s_is_in_closetable = 1;       // 已经在close表中了  
269       
270             if ( curr_node->s_x == end_node->s_x && curr_node->s_y == end_node->s_y )// 终点在close中,结束  
271             {  
272                 is_found = 1;  
273                 break;  
274             }  
275       
276             get_neighbors( curr_node, end_node );           // 对邻居的处理  
277       
278             if ( open_node_count == 0 )             // 没有路径到达  
279             {  
280                 is_found = 0;  
281                 break;  
282             }  
283         }    
284         if ( is_found )  
285         {  
286             curr_node = end_node;     
287             while( curr_node )  
288             {  
289                 ///////////////////////////////////绕过斜线两边有障碍物///////////////////////////////////////
290                 //此处绕过障碍物的方法是在寻找到路径点后通过简单地绕点来达到目的,找到的路径不是最佳的,需要进一步优化
291                 AStarNode* temp=curr_node->s_parent;
292                 if(temp!=NULL)
293                 {
294                 if((curr_node->s_x-1==temp->s_x&&curr_node->s_y+1==temp->s_y)||(curr_node->s_x+1==temp->s_x&&curr_node->s_y-1==temp->s_y))
295                 {
296                     if(curr_node->s_y>temp->s_y)
297                     {
298                         if(map_maze[curr_node->s_x][curr_node->s_y-1].s_style==3)
299                         {
300                                 AStarNode* temp1=&map_maze[curr_node->s_x+1][curr_node->s_y];
301                             temp1->s_parent=curr_node->s_parent;
302                             curr_node->s_parent=temp1;
303                         }
304                         else if(map_maze[curr_node->s_x+1][curr_node->s_y].s_style==3)
305                         {
306                             AStarNode* temp1=&map_maze[curr_node->s_x][curr_node->s_y-1];
307                             temp1->s_parent=curr_node->s_parent;
308                             curr_node->s_parent=temp1;
309                         }
310                     }
311                 
312                     else 
313                     {
314                         if(map_maze[curr_node->s_x][curr_node->s_y+1].s_style==3)
315                         {
316                             AStarNode* temp1=&map_maze[curr_node->s_x-1][curr_node->s_y];
317                             temp1->s_parent=curr_node->s_parent;
318                             curr_node->s_parent=temp1;
319                         }
320                             
321                         else if(map_maze[curr_node->s_x-1][curr_node->s_y].s_style==3)
322                         {
323                             AStarNode* temp1=&map_maze[curr_node->s_x][curr_node->s_y+1];
324                             temp1->s_parent=curr_node->s_parent;
325                             curr_node->s_parent=temp1;
326                         }
327 
328                     }
329                 }
330                                 
331                     else 
332                     {
333                         if((curr_node->s_x+1==temp->s_x&&curr_node->s_y+1==temp->s_y)||(temp->s_x+1==curr_node->s_x&&temp->s_y+1==curr_node->s_y))
334                         {
335                             if(curr_node->s_x<temp->s_x)
336                             {
337                                 if(map_maze[curr_node->s_x][curr_node->s_y+1].s_style==3)
338                                 {
339                                 AStarNode* temp1=&map_maze[curr_node->s_x+1][curr_node->s_y];
340                                 temp1->s_parent=curr_node->s_parent;
341                                 curr_node->s_parent=temp1;
342                                 }
343                                 else if(map_maze[curr_node->s_x+1][curr_node->s_y].s_style==3)
344                                 {
345                                     AStarNode* temp1=&map_maze[curr_node->s_x][curr_node->s_y+1];
346                                     temp1->s_parent=curr_node->s_parent;
347                                     curr_node->s_parent=temp1;
348                                 }
349                             }
350                             else if(curr_node->s_x>temp->s_x)
351                             {
352                                 if(map_maze[curr_node->s_x-1][curr_node->s_y].s_style==3)
353                                 {
354                                 AStarNode* temp1=&map_maze[curr_node->s_x][curr_node->s_y-1];
355                                 temp1->s_parent=curr_node->s_parent;
356                                 curr_node->s_parent=temp1;
357                                 }
358                                 else if(map_maze[curr_node->s_x][curr_node->s_y-1].s_style==3)
359                                 {
360                                     AStarNode* temp1=&map_maze[curr_node->s_x-1][curr_node->s_y];
361                                     temp1->s_parent=curr_node->s_parent;
362                                     curr_node->s_parent=temp1;
363                                 }
364                             }
365                         }
366                     }
367                 }
368     ///////////////////////////////////绕过斜线两边有障碍物///////////////////////////////////////
369                 //////////////////////////////////////////////////////////////////////////
370                 path_stack[++top] = curr_node;  
371                 curr_node = curr_node->s_parent;  
372             }  
373 
374             while( top >= 0 )        // 输出路径
375             {  
376                 if ( top > 0 )  
377                 {  
378                     printf("(%d,%d)-->", path_stack[top]->s_x, path_stack[top--]->s_y);  
379                 }  
380                 else  
381                 {  
382                     printf("(%d,%d)", path_stack[top]->s_x, path_stack[top--]->s_y);  
383                 }  
384             }  
385         }  
386         else  
387         {  
388             printf("没有找到路径");  
389         }  
390       
391         puts("");  
392       
393         return 0;  
394     }  
View Code

运行效果如下图所示:

 此代码还存在改进的地方,此处仅作参考

 

 

注:本文章图形及主要参考文章来自下面的网址

http://www.policyalmanac.org/games/aStarTutorial.htm

http://zhyt710.iteye.com/blog/739803

http://blog.csdn.net/shanshanpt/article/details/8977512

posted @ 2015-07-16 21:53  Cratial  阅读(1769)  评论(0编辑  收藏  举报