A*搜寻算法

启发式搜索算法
收藏
0有用+1
0
A*搜寻算法俗称A星算法。A*算法是比较流行的启发式搜索算法之一,被广泛应用于路径优化领域[。它的独特之处是检查最短路径中每个可能的节点时引入了全局信息,对当前节点距终点的距离做出估计,并作为评价该节点处于最短路线上的可能性的量度。 [1]
中文名
A*搜寻算法
外文名
A-star Algorithm
别    名
A星算法
应用领域
图最优路径搜索/查找
类    型
求出最低通过成本的算法

算法描述

播报
编辑
A*改变它自己行为的能力基于启发式代价函数,启发式函数在游戏中非常有用。在速度和精确度之间取得折衷将会让你的游戏运行得更快。在很多游戏中,你并不真正需要得到最好的路径,仅需要近似的就足够了。而你需要什么则取决于游戏中发生着什么,或者运行游戏的机器有多快。 假设你的游戏有两种地形,平原和山地,在平原中的移动代价是1而在山地的是3,那么A星算法就会认为在平地上可以进行三倍于山地的距离进行等价搜寻。 这是因为有可能有一条沿着平原到山地的路径。把两个邻接点之间的评估距离设为1.5可以加速A*的搜索过程。然后A*会将3和1.5比较,这并不比把3和1比较差。然而,在山地上行动有时可能会优于绕过山脚下进行行动。所以花费更多时间寻找一个绕过山的算法并不经常是可靠的。 同样的,想要达成这样的目标,你可以通过减少在山脚下的搜索行为来打到提高A星算法的运行速率。若想如此可以将A星算法的山地行动耗费从3调整为2即可。这两种方法都会给出可靠地行动策略。
用C语言实现A*最短路径搜索算法,作者 Tittup frog(跳跳蛙)。
#include <stdio.h> #include <math.h> #define MaxLength 100    //用于优先队列(Open表)的数组 #define Height     15    //地图高度 #define Width      20    //地图宽度 #define Reachable   0    //可以到达的结点 #define Bar         1    //障碍物 #define Pass        2    //需要走的步数 #define Source      3    //起点 #define Destination 4    //终点 #define Sequential  0    //顺序遍历 #define NoSolution  2    //无解决方案 #define Infinity    0xfffffff #define East       (1 << 0) #define South_East (1 << 1) #define South      (1 << 2) #define South_West (1 << 3) #define West       (1 << 4) #define North_West (1 << 5) #define North      (1 << 6) #define North_East (1 << 7) typedef struct {     signed char x, y; } Point; const Point dir[8] = {     {0, 1},   // East     {1, 1},   // South_East     {1, 0},   // South     {1, -1},  // South_West     {0, -1},  // West     {-1, -1}, // North_West     {-1, 0},  // North     {-1, 1}   // North_East }; unsigned char within(int x, int y) {     return (x >= 0 && y >= 0         && x < Height && y < Width); } typedef struct {     int x, y;     unsigned char reachable, sur, value; } MapNode; typedef struct Close {     MapNode *cur;     char vis;     struct Close *from;     float F, G;     int H; } Close; typedef struct //优先队列(Open表) {     int length;        //当前队列的长度     Close* Array[MaxLength];    //评价结点的指针 } Open; static MapNode graph[Height][Width]; static int srcX, srcY, dstX, dstY;    //起始点、终点 static Close close[Height][Width]; // 优先队列基本操作 void initOpen(Open *q)    //优先队列初始化 {     q->length = 0;        // 队内元素数初始为0 } void push(Open *q, Close cls[Height][Width], int x, int y, float g) {    //向优先队列(Open表)中添加元素     Close *t;     int i, mintag;     cls[x][y].G = g;    //所添加节点的坐标     cls[x][y].F = cls[x][y].G + cls[x][y].H;     q->Array[q->length++] = &(cls[x][y]);     mintag = q->length - 1;     for (i = 0; i < q->length - 1; i++)     {         if (q->Array[i]->F < q->Array[mintag]->F)         {             mintag = i;         }     }     t = q->Array[q->length - 1];     q->Array[q->length - 1] = q->Array[mintag];     q->Array[mintag] = t;    //将评价函数值最小节点置于队头 } Close* shift(Open *q) {     return q->Array[--q->length]; } // 地图初始化操作 void initClose(Close cls[Height][Width], int sx, int sy, int dx, int dy) {    // 地图Close表初始化配置     int i, j;     for (i = 0; i < Height; i++)     {         for (j = 0; j < Width; j++)         {             cls[i][j].cur = &graph[i][j];        // Close表所指节点             cls[i][j].vis = !graph[i][j].reachable;        // 是否被访问             cls[i][j].from = NULL;                // 所来节点             cls[i][j].G = cls[i][j].F = 0;             cls[i][j].H = abs(dx - i) + abs(dy - j);    // 评价函数值         }     }     cls[sx][sy].F = cls[sx][sy].H;            //起始点评价初始值     //    cls[sy][sy].G = 0;                        //移步花费代价值     cls[dx][dy].G = Infinity; } void initGraph(const int map[Height][Width], int sx, int sy, int dx, int dy) {    //地图发生变化时重新构造地     int i, j;     srcX = sx;    //起点X坐标     srcY = sy;    //起点Y坐标     dstX = dx;    //终点X坐标     dstY = dy;    //终点Y坐标     for (i = 0; i < Height; i++)     {         for (j = 0; j < Width; j++)         {             graph[i][j].x = i; //地图坐标X             graph[i][j].y = j; //地图坐标Y             graph[i][j].value = map[i][j];             graph[i][j].reachable = (graph[i][j].value == Reachable);    // 节点可到达性             graph[i][j].sur = 0; //邻接节点个数             if (!graph[i][j].reachable)             {                 continue;             }             if (j > 0)             {                 if (graph[i][j - 1].reachable)    // left节点可以到达                 {                     graph[i][j].sur |= West;                     graph[i][j - 1].sur |= East;                 }                 if (i > 0)                 {                     if (graph[i - 1][j - 1].reachable                         && graph[i - 1][j].reachable                         && graph[i][j - 1].reachable)    // up-left节点可以到达                     {                         graph[i][j].sur |= North_West;                         graph[i - 1][j - 1].sur |= South_East;                     }                 }             }             if (i > 0)             {                 if (graph[i - 1][j].reachable)    // up节点可以到达                 {                     graph[i][j].sur |= North;                     graph[i - 1][j].sur |= South;                 }                 if (j < Width - 1)                 {                     if (graph[i - 1][j + 1].reachable                         && graph[i - 1][j].reachable                         && map[i][j + 1] == Reachable) // up-right节点可以到达                     {                         graph[i][j].sur |= North_East;                         graph[i - 1][j + 1].sur |= South_West;                     }                 }             }         }     } } int bfs() {     int times = 0;     int i, curX, curY, surX, surY;     unsigned char f = 0, r = 1;     Close *p;     Close* q[MaxLength] = { &close[srcX][srcY] };     initClose(close, srcX, srcY, dstX, dstY);     close[srcX][srcY].vis = 1;     while (r != f)     {         p = q[f];         f = (f + 1) % MaxLength;         curX = p->cur->x;         curY = p->cur->y;         for (i = 0; i < 8; i++)         {             if (! (p->cur->sur & (1 << i)))             {                 continue;             }             surX = curX + dir[i].x;             surY = curY + dir[i].y;             if (! close[surX][surY].vis)             {                 close[surX][surY].from = p;                 close[surX][surY].vis = 1;                 close[surX][surY].G = p->G + 1;                 q[r] = &close[surX][surY];                 r = (r + 1) % MaxLength;             }         }         times++;     }     return times; } int astar() {    // A*算法遍历     //int times = 0;     int i, curX, curY, surX, surY;     float surG;     Open q; //Open表     Close *p;     initOpen(&q);     initClose(close, srcX, srcY, dstX, dstY);     close[srcX][srcY].vis = 1;     push(&q, close, srcX, srcY, 0);     while (q.length)     {    //times++;         p = shift(&q);         curX = p->cur->x;         curY = p->cur->y;         if (!p->H)         {             return Sequential;         }         for (i = 0; i < 8; i++)         {             if (! (p->cur->sur & (1 << i)))             {                 continue;             }             surX = curX + dir[i].x;             surY = curY + dir[i].y;             if (!close[surX][surY].vis)             {                 close[surX][surY].vis = 1;                 close[surX][surY].from = p;                 surG = p->G + sqrt((curX - surX) * (curX - surX) + (curY - surY) * (curY - surY));                 push(&q, close, surX, surY, surG);             }         }     }     //printf("times: %d\n", times);     return NoSolution; //无结果 } const int map[Height][Width] = {     {0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,1},     {0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1},     {0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,1},     {0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0},     {0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1},     {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0},     {0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0},     {0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},     {0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0},     {0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0},     {0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0},     {0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0},     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0},     {0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,1},     {0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0} }; const char Symbol[5][3] = { "□", "▓", "▽", "☆", "◎" }; void printMap() {     int i, j;     for (i = 0; i < Height; i++)     {         for (j = 0; j < Width; j++)         {             printf("%s", Symbol[graph[i][j].value]);         }         puts("");     }     puts(""); } Close* getShortest() {    // 获取最短路径     int result = astar();     Close *p, *t, *q = NULL;     switch(result)     {     case Sequential:    //顺序最近         p = &(close[dstX][dstY]);         while (p)    //转置路径         {             t = p->from;             p->from = q;             q = p;             p = t;         }         close[srcX][srcY].from = q->from;         return &(close[srcX][srcY]);     case NoSolution:         return NULL;     }     return NULL; } static Close *start; static int shortestep; int printShortest() {     Close *p;     int step = 0;     p = getShortest();     start = p;     if (!p)     {         return 0;     }     else     {         while (p->from)         {             graph[p->cur->x][p->cur->y].value = Pass;             printf("(%d,%d)→\n", p->cur->x, p->cur->y);             p = p->from;             step++;         }         printf("(%d,%d)\n", p->cur->x, p->cur->y);         graph[srcX][srcY].value = Source;         graph[dstX][dstY].value = Destination;         return step;     } } void clearMap() {    // Clear Map Marks of Steps     Close *p = start;     while (p)     {         graph[p->cur->x][p->cur->y].value = Reachable;         p = p->from;     }     graph[srcX][srcY].value = map[srcX][srcY];     graph[dstX][dstY].value = map[dstX][dstY]; } void printDepth() {     int i, j;     for (i = 0; i < Height; i++)     {         for (j = 0; j < Width; j++)         {             if (map[i][j])             {                 printf("%s ", Symbol[graph[i][j].value]);             }             else             {                 printf("%2.0lf ", close[i][j].G);             }         }         puts("");     }     puts(""); } void printSur() {     int i, j;     for (i = 0; i < Height; i++)     {         for (j = 0; j < Width; j++)         {             printf("%02x ", graph[i][j].sur);         }         puts("");     }     puts(""); } void printH() {     int i, j;     for (i = 0; i < Height; i++)     {         for (j = 0; j < Width; j++)         {             printf("%02d ", close[i][j].H);         }         puts("");     }     puts(""); } int main(int argc, const char **argv) {     initGraph(map, 0, 0, 0, 0);     printMap();     while (scanf("%d %d %d %d", &srcX, &srcY, &dstX, &dstY) != EOF)     {         if (within(srcX, srcY) && within(dstX, dstY))         {             if (shortestep = printShortest())             {                 printf("从(%d,%d)到(%d,%d)的最短步数是: %d\n",                     srcX, srcY, dstX, dstY, shortestep);                 printMap();                 clearMap();                 bfs();                 //printDepth();                 puts((shortestep == close[dstX][dstY].G) ? "正确" : "错误");                 clearMap();             }             else             {                 printf("从(%d,%d)不可到达(%d,%d)\n",                     srcX, srcY, dstX, dstY);             }         }         else         {             puts("输入错误!");         }     }     return (0); }

性质

播报
编辑

可采纳性

在一些问题的求解过程中,如果存在最短路径,无论在什么情况之下,如果一个搜索算法都能够保证找到这条最短路径,则称这样的搜索算法就具有可采纳性。 [2]

单调性

A*算法扩展的所有节点的序列的f值是递增的,那么它最先生成的路径一定就是最短的。

信息性

如何判断两种策略哪一个更好呢?具有的启发信息越多,搜索的状态就越少,越容易找到最短路径,这样的策略就更好。在两个启发策略中,如果有h(n1)
h(n2),则表示策略h:比h,具有更多的启发信息,同时h(n)越大表示它所搜索的空间数就越少。但是同时需要注意的是:信息越多就需要更多的计算时间,从而有可能抵消因为信息多而减少的搜索空间所带来的好处。

缺陷

播报
编辑
A*算法进行下一步将要走的节点的搜索的时候,每次都是选择F值最小的节点,因此找到的是最优路径。但是正因为如此A*算法每次都要扩展当前节点的全部后继节点,运用启发函数计算它们的F值,然后选择F值最小的节点作为下一步走的节点。在这个过程中,OPEN表需要保存大量的节点信息,不仅存储量大是一个问题,而且在查找F值最小的节点时,需要查询的节点也非常多,当然就非常耗时,这个问题就非常严重了。再加上如果游戏地图庞大,路径比较复杂,路径搜索过程则可能要计算成千上万的节点,计算量非常巨大。因此,搜索一条路径需要一定的时间,这就意味着游戏运行速度降低。 [2]