博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
寻路算法A*, JPS(跳点搜索)的一些杂谈
阅读量:4963 次
发布时间:2019-06-12

本文共 3720 字,大约阅读时间需要 12 分钟。

A*是一个比较经典的启发式寻路算法。是基于dijkstra算法,但是加入了启发函数,使路径搜索效率更高。实现起来很简单。不过要做到通用性高,比如支持各种不同类型的地图,甚至不仅仅是地图,而是个图结构如解决拼图游戏N-puzzle会用到的,就需要多花点心思。用C++实现的话,可以使用模板来适应不同的需要。也可以使用类继承。

template 
static vector
search(Map
&map, NodeType start, NodeType goal, Heuristic heuristic) { map.initialize(start, goal); map.open_node(start, 0, heuristic(start, goal), start); // Get started. while (map.open_node_available()) { NodeType top_node = map.close_front_open_node(); if (map.nodes_equal(top_node, goal)) return map.get_path(top_node); // Stop and return the path found. const vector
> &&edges = map.edges(top_node); for (auto edge : edges) { // For each qualified edge evaluate target node. NodeType node_to_evaluate = edge.to_; CostType g = map.current_cost(top_node) + edge.cost_; CostType h = heuristic(node_to_evaluate, goal); if (map.node_unexplored(node_to_evaluate)) { map.open_node(node_to_evaluate, g, h, top_node); } else if (map.cost_greater(map.current_cost(node_to_evaluate), g)) { if (map.node_open(node_to_evaluate)) { map.increase_node_priority(node_to_evaluate, g, h, top_node); } else { // Won't reach here if heuristic is consistent(monotone). map.reopen_node(node_to_evaluate, g, h, top_node); } } } } return vector
(); // No path found. Return an empty path. }

 

很久以前在大学里开发自制的即时战略游戏,第一次实现这个算法。不过那个时候互联网还不流行,很难找到相关资料,所以用现在的眼光看实现得不够好。尤其是open列表,只是用了个有序链表。现在看来,可以使用一个优先队列(priority queue),毕竟每次从open列表只需要取第一个元素,其他元素的次序并不重要。C++的STL提供了一个优先队列,不过很不幸,并没有提供改变某个元素优先度的操作。所以,要么自己实现一个优先队列,要么在STL的基础上添加这个功能。如果看下STL的源代码就会发现STL的优先队列实际上是一个二叉堆(binary heap)。所以,只要在这个基础上添加个函数percolate_up()函数就可以了。当然,偷懒一点,修改元素优先度后再用std::make_heap()重新生成堆也可以替代,不过效率就差了。

// Percolate up an element at the index specified.template
static void percolate_up(Container &container, int index, LessPriority less_priority) { auto value = container[index]; int hole = index; int parent = (hole - 1) / 2; while (hole > 0 && less_priority(container[parent], value)) { container[hole] = container[parent]; hole = parent; parent = (hole - 1) / 2; } container[hole] = value;}

 

再改进一下,可以用HOT(heap on top)来优化。也就是建立一个数据结构,只在最上面使用优先队列,而其余的元素则放在其他的容器里,比如分成很多个bucket的vector。这样做的好处是可以缩小优先队列的规模,那些不大可能被open的元素则不进队列,除非队列已经排空需要把一个bucket转化为新的优先队列。

 

另外一项技术,就是Jump Point Search(JPS或者所谓的跳点搜索)。这是一个近年来发现的高效寻路算法。不过有一个限制就是只能在规则的格子地图上寻路,而且图上的点或边不能带权重,也就是不能有复杂的地形,只支持平坦和障碍两种地形。其思想就是跳过矩形平坦区域的大量对称路径,只寻找所谓的跳跃点,作为搜索的节点。这样做的好处是裁剪了矩形区域内大量的节点,使open list中的节点数相对较少。不过JPS有个缺点是每生成一个节点,也就是要找到一个跳跃点,相比较一般的A*算法,是比较昂贵的。幸好通常来说,得到的收益更多些。所以,在适用的情况下,还是推荐使用JPS的。

具体的实现,主要有两部分。第一部分,从open list中取一个最佳节点,然后从几个特定方向展开搜索,把每个方向得到的跳跃点,加入open list里。第二部分,就是找到一个跳跃点。

对于起始点,可以向所有方向展开搜索。对于其他节点,要看父节点指向本节点的方向,向所有自然邻居和被迫邻居节点方向进行搜索。

例如下图的例子,对于节点n和父节点p和障碍x,+是n的自然邻居,也就是说从p到n到+是最佳路径。如果x不是障碍,从p到n到-不是最佳路径,因为从p到x到-最近。但是如果x是障碍,那么从p到n到-就是最佳路径了,所以这时候称-为被迫邻居。

- + + x n +p x -

以上是n和p对角的例子。还有种情况是n和p是直线:

x x - p n +x x -

 

搜寻跳跃点是递归进行的。首先判断一个节点是否是跳跃点。如果这个点有被迫邻居,那么这个节点就是跳跃点。第二种情况,如果这个节点是目的地节点那么也当作跳跃点。如果不是,那么就递归地沿本来方向继续搜寻下去。对于对角方向要额外多做两步,即先对其相应的(左右两个)直线方向进行搜索,如果找到跳跃点,就把自身也当作跳跃点返回。如果直线没找到,那么就一样继续按对角方向递归寻找跳跃点,并返回那个跳跃点。

以下是使用A*算法和JPS算法搜索一个10x10的格子地图得到的结果比较(x是障碍,S是起始点,G是终点,@是最佳路径,o是open节点,-是closed节点)

大致可以看出JPS是如何通过jump point跳过矩形区域的。

A*:

ooox-S-- oo@Gx-@--oo@xxxx@--o@-x--xx@-@xxx---x@--@-x-x-x@-o-@x-x-x@-o--@@x-x@-oo---@@@-- oo-------
 

JPS:

x S     @Gx  @    xxxx      x  xx  @xxx - x     x-x-x     x x x     @@x-x@      @ @

代码和示例放在了github上:

 

转载于:https://www.cnblogs.com/silmerusse/p/3973336.html

你可能感兴趣的文章
P3119 [USACO15JAN]草鉴定Grass Cownoisseur
查看>>
JavaSE教程-04Java中循环语句for,while,do···while-思维导图
查看>>
周记 2015.03.15
查看>>
杂记01
查看>>
angular controller as syntax vs scope
查看>>
求最小正整数x,A^x=1(mod M)求阶模板
查看>>
PHP学习日记 函数
查看>>
中断触发方式的比较(转载)
查看>>
maven 学习1 -安装maven 并执行编译命令
查看>>
primace 5.0软件的Debug ware 功能的使用方法简介
查看>>
_stdcall,_cdecl区别
查看>>
【STL源码剖析读书笔记】自己实现简单的空间配置器MyAllocator
查看>>
iOS开发网络篇—数据缓存
查看>>
Nginx与Lua
查看>>
DB2 解锁表
查看>>
MySQL学习笔记——存储过程
查看>>
Java数据结构——优先级队列
查看>>
LeetCode 之Find Minimum in Rotated Sorted Array
查看>>
[工具] 护眼宝 – 傻瓜版屏幕蓝光过滤应用[Win/Android]
查看>>
2.1 线性表的顺序存储结构
查看>>