A*算法是一种启发式的BFS,目的就是找到到达目标位置的最短路径。启发式函数如下:

f(x) = g(x) + h(x)

g(x)是对出发点到达当前点距离的估约,h(x)是当前点到终点距离的估约。算法是一个广度优先搜索的过程,但是搜索时的可选集合是一个优先级队列,f(x)越小优先级越高。

算法过程描述

1。用起点初始优先级队列opened;

2。在opened中取最小f(x)的坐标节点,如果该点就是终点,则成功找到返回,否则,向该点的相邻方向寻找可行的下一个移动节点,计算每一个可行节点的f(x)保存,放入opened队列中,继续步骤2;

关于最短路径的求解还应该维护一个映射表,保存移动过程的上一个节点,之后再从终点往前遍历到起点便是整条路径了。

求单源最短路径的迪杰斯特拉算法其实就是当h(x)=0的A*算法。以下是算法的伪码(摘自维基百科

  1. function A*(start,goal)
  2. closedset := the empty set // The set of nodes already evaluated.
  3. openset := {start} // The set of tentative nodes to be evaluated, initially containing the start node
  4. came_from := the empty map // The map of navigated nodes.
  5.  
  6. g_score[start] := 0 // Cost from start along best known path.
  7. // Estimated total cost from start to goal through y.
  8. f_score[start] := g_score[start] + heuristic_cost_estimate(start, goal)
  9.  
  10. while openset is not empty
  11. current := the node in openset having the lowest f_score[] value
  12. if current = goal
  13. return reconstruct_path(came_from, goal)
  14.  
  15. remove current from openset
  16. add current to closedset
  17. for each neighbor in neighbor_nodes(current)
  18. tentative_g_score := g_score[current] + dist_between(current,neighbor)
  19. tentative_f_score := tentative_g_score + heuristic_cost_estimate(neighbor, goal)
  20. if neighbor in closedset and tentative_f_score >= f_score[neighbor]
  21. continue
  22.  
  23. if neighbor not in openset or tentative_f_score < f_score[neighbor]
  24. came_from[neighbor] := current
  25. g_score[neighbor] := tentative_g_score
  26. f_score[neighbor] := tentative_f_score
  27. if neighbor not in openset
  28. add neighbor to openset
  29.  
  30. return failure

详细设计实现

游戏中自动寻路的场景会被划分成网格,每一个网格可以是阻挡或者通路,在当前网格中可以向周围8个相邻方向移动(左,右,上,下,左上,左下,右上,右下)。运用A*算法从start寻路到end,启发式函数可以这样设计:

1。g(x)设计:初始化起点的g(start) = 0。假设当前位置是cur,下一个位置next若是cur从对角移动过来的(即左上,左下,右上,右下)则g(next) = g(cur) + 14,若是从水平或者垂直方向移动过来的,则g(next) = g(cur) + 10。常数14和10是这么得来的:勾股定理中,假设两直角边都是1,对角线则是1.414,各自放大10倍取整就是10和14;

2。h(x)设计:假设当前节点是(xcur,ycur),终点是(xend,yend),h(x) = abs(xcur – xend) + abs(ycur- yend);

优先级队列可以用最小堆来实现,但是搜索过程中可能出现这样的情况:新添加一个可用节点时,该节点已经在opened队列中了,且新节点有更小的f(x)值,这时候要更新队列中这个节点并重新调整堆。查找新入的节点是否已经在队列中了,单纯用最小堆实现时间复杂度是O(n),可以再加一个hash表,维护坐标点到最小堆中节点的映射,复杂度降为O(1)。代码实现如下:

  1. #define TI(x, y) ((x)*H+(y))
  2.  
  3. struct OpenedNode{
  4. int _f, _g;
  5. int _x, _y;
  6. int _parent;
  7. OpenedNode(int f = -1, int g = -1, int x = -1, int y = -1, int parent = -1):_f(f),_g(g),_x(x),_y(y),_parent(parent) {}
  8.  
  9. };
  10.  
  11. template <size_t W, size_t H>
  12. class OpenedHeap {
  13. OpenedNode _heap[W*H];
  14. int _set[W*H];
  15. int _sz;
  16.  
  17. void move_top(int icur) {
  18. OpenedNode ON = _heap[icur];
  19. while (icur > 0) {
  20. int iparent = (icur-1) / 2;
  21. if (_heap[iparent]._f <= ON._f) break;
  22.  
  23. _heap[icur] = _heap[iparent];
  24.  
  25. // update
  26. _set[TI(_heap[icur]._x,_heap[icur]._y)] = icur;
  27.  
  28. icur = iparent;
  29. }
  30. _heap[icur] = ON;
  31. _set[TI(_heap[icur]._x, _heap[icur]._y)] = icur;
  32. }
  33.  
  34. void move_bottom(int icur) {
  35. OpenedNode ON = _heap[icur];
  36. while (true) {
  37. int ileft = icur * 2 + 1;
  38. if (ileft >= _sz) break;
  39.  
  40. int iright = icur * 2 + 2;
  41.  
  42. int ismaller = iright < _sz && _heap[iright]._f < _heap[ileft]._f? iright:ileft;
  43.  
  44. if (_heap[ismaller]._f < ON._f) {
  45. _heap[icur] = _heap[ismaller];
  46. _set[TI(_heap[icur]._x, _heap[icur]._y)] = icur;
  47. icur = ismaller;
  48. }else break;
  49. }
  50. _heap[icur] = ON;
  51. _set[TI(_heap[icur]._x, _heap[icur]._y)] = icur;
  52. }
  53.  
  54. public:
  55. OpenedHeap():_sz(0) {
  56. memset(_set, -1, sizeof(_set));
  57. }
  58.  
  59. void push(const OpenedNode &ON) {
  60. _heap[_sz] = ON;
  61. int icur = _sz++;
  62.  
  63. move_top(icur);
  64. // ensure();
  65. }
  66.  
  67. void pop(OpenedNode &ret) {
  68. ret = _heap[0];
  69. _set[TI(_heap[0]._x,_heap[0]._y)] = -1;
  70. assert(_sz>0);
  71. _heap[0] = _heap[--_sz];
  72. if (_sz > 0)
  73. move_bottom(0);
  74. // ensure();
  75. }
  76.  
  77. bool Get(int x, int y, OpenedNode &ret) {
  78. if (_set[TI(x,y)] >= 0) {
  79. ret = _heap[_set[TI(x,y)]];
  80. return true;
  81. }
  82. return false;
  83. }
  84.  
  85. bool make_smaller(int x, int y, int f, int g, int parent) {
  86. int icur = _set[TI(x,y)];
  87. assert(_heap[icur]._x == x && _heap[icur]._y == y);
  88. if (icur < 0) return false;
  89. if (f == _heap[icur]._f) return true;
  90. bool top = false;
  91. if (f < _heap[icur]._f) top = true;
  92. _heap[icur]._f = f;
  93. _heap[icur]._g = g;
  94. _heap[icur]._parent = parent;
  95.  
  96. if (top) {
  97. move_top(icur);
  98. }else {
  99. move_bottom(icur);
  100. }
  101.  
  102. //ensure();
  103. return true;
  104. }

注意事项:

这也是我实现中犯过的错:

1。移动过程中不能从子节点回到直接父节点,这个是理所当然,所以实现时候在往8个方向移动时要屏蔽父节点。

2。已经在closed中的节点是有可能重新加入opened中的,所以在移动时得到一个新的节点时还要判断是不是比closed中这个节点更cheap(若closed中已经存在这个节点了)。

  1.  

全部代码在此。可以用在游戏地图动态生成中实现的地图来测试A*寻路,下面是在一个35*100的地图中的自动寻路的效果(字符'-'代表路径)

游戏寻路A*算法的更多相关文章

  1. 地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了

    地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了 四叉树对于区域查询,效率比较高. 原理图

  2. Unity3D 2D游戏中寻径算法的一些解决思路

    需求 unity3d的3d开发环境中,原生自带了Navigation的组件,可以很便捷快速的实现寻路功能.但是在原生的2d中并没有相同的功能. 现在国内很多手机游戏都有自动寻路的功能,或者游戏中存在一 ...

  3. 原因好消息: PSP游戏自己主动算法设计(两)

    这是我们讲的传说中的一项措施A×算法.事实上,类上传之前似小件,下面我们分析一下它去 毕竟,在游戏程序,我们从移动一个点到另一个点.和得到的轨迹的最短距离,类别似这样的算法以及几个.运营效率几乎是相同 ...

  4. H5版俄罗斯方块(3)---游戏的AI算法

    前言: 算是"long long ago"的事了, 某著名互联网公司在我校举行了一次"lengend code"的比赛, 其中有一题就是"智能俄罗斯方 ...

  5. 游戏碰撞OBB算法(java代码)

    业务需求      游戏2D型号有圆形和矩形,推断说白了就是碰撞检测 :      1.圆形跟圆形是否有相交      2.圆形跟矩形是否相交       3.矩形和矩形是否相交           ...

  6. cocos2d 消除类游戏简单的算法 (一)

    1. 游戏视频演示 2.三消游戏我的理解 上面视频中的游戏.我做了2个星期时间,仅仅能算个简单Demo,还有bug.特效也差点儿没有.感觉三消游戏主要靠磨.越磨越精品. 市场上三消游戏已经超级多了.主 ...

  7. LeetCode | 289. 生命游戏(原地算法/位运算)

    记录dalao的位运算骚操作 根据百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机. 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细 ...

  8. 51nod 1459 迷宫游戏 (最短路径—Dijkstra算法)

    题目链接 中文题,迪杰斯特拉最短路径算法模板题. #include<stdio.h> #include<string.h> #define INF 0x3f3f3f3f ],v ...

  9. html5游戏-包围盒检测算法

    矩形包围盒算法:检测2个矩形是否重叠,在这样情况下要判断2个矩形是否碰撞只需要比较两个矩形顶点的坐标即可.假设矩形A用(x1,y1)表示左上角,(x2,y2)表示右下角,矩形B用(x3,y3)表示左上 ...

随机推荐

  1. Java学习_注解

    使用注解 注解是放在Java源码的类.方法.字段.参数前的一种特殊"注释". 1 // this is a component: 2 @Resource("hello&q ...

  2. Sharding jdbc 强制路由策略(HintShardingStrategy)使用记录

    背景 随着项目运行时间逐渐增加,数据库中的数据也越来越多,虽然加索引,优化查询,但是数据量太大,还是会影响查询效率,也给数据库增加了负载. 再加上冷数据基本不使用的场景,决定采用分表来处理数据,从而来 ...

  3. Java连接MySQL数据库——含详细步骤和代码

    工具:eclipse.MySQL.MySQL连接驱动:mysql-connector-java-5.1.45.jar 首先要下载Connector/J地址:http://www.mysql.com/d ...

  4. 有了它(powermock)再也不担心单元测试不达标了

    为什么要写单元测试 优点:单元测试可以减少bug率,提升代码的质量.还可以通过单元测试来熟悉业务. 公司硬性要求:有些公司可能还会强制要求,每次新增代码.或者变更代码单测覆盖率要达到多少比例才能申请代 ...

  5. 庐山真面目之十微服务架构 Net Core 基于 Docker 容器部署 Nginx 集群

    庐山真面目之十微服务架构 Net Core 基于 Docker 容器部署 Nginx 集群 一.简介      前面的两篇文章,我们已经介绍了Net Core项目基于Docker容器部署在Linux服 ...

  6. zigzag压缩算法

    前文 Base 128 Varints 编码(压缩算法) 介绍了Base 128 Varints这种对数字传输的编码,了解到了这种编码方式是为了最大程度压缩数字的.但是,在前文里,我们只谈论到了正数的 ...

  7. Java基础经典案例

    案例列表 01减肥计划switch版本 02减肥计划if版本 03逢七跳过 04不死神兔 05百钱白鸡 06数组元素求和 07判断两个数组是否相同 08查找元素在数组中的索引 09数组元素反转 10评 ...

  8. node获取请求参数的方法get与post请求

    1.get请求 get的请求参数是携带在url中的,因此需要引入url模块对请求进行解析,再使用url.parse()方法,get请求多用于页面跳转.表单等请求中,例如page页码.表单账号密码等 先 ...

  9. Beta冲刺——第十天(补发)

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...

  10. 园子的品牌专区上新:NoSQL 数据库佼佼者 Aerospike

    品牌专区是园子去年推出的新楼盘,为优秀的科技企业在园子里提供一个地方,展示自己的品牌,分享自己的技术内容. 最近我们和国外领先的 NoSQL 数据库厂商 Aerospike 达成了合作,入驻了园子的品 ...