A* 搜索算法

  • Ⅰ 前言
  • Ⅱ 算法解析
  • Ⅲ 如何实现游戏寻路问题
  • Ⅳ 总结

Ⅰ 前言

你可能玩过魔兽世界,仙剑奇侠和英雄联盟这类 MMRPG 游戏,在这些游戏中,有一个非常重要的功能,就是人物角色自动寻路。当人物处于游戏地图中的某个位置的时候,我们用鼠标点击另外一个相对较远的位置,人物就会自动地绕过障碍物走过去。那么这个功能是如何实现的呢?这篇文章我们就来探索一下这个功能。

Ⅱ 算法解析

实际上,这是一个非常典型的搜索问题,人物的起点就是他当下所在的位置,终点就是鼠标点击的位置。我们需要在地图中,找一条从起点到终点的路径,这条路径要绕过地图中所有障碍物,并且看起来要是一种非常聪明的走法。所谓聪明,笼统地解释就是,走的路不能太绕。理论上讲,最短路径显然是最聪明的走法,是这个问题的最优解。

但是,在前面图的最短路径的讲解中,我说过,如果图非常大,那 Dijkstra 最短路径算法的执行耗时会很多。在真实的软件开发中,我们面对的是超级大的地图和海量的寻路请求,算法的执行效率太低,这显然是无法接受的。

【数据结构与算法】->算法->地图软件的最优路线是如何计算的?

实际上,像出行路线规划、游戏寻路,这些真实软件开发中的问题,一般情况下,我们都不需要非得求最优解(也就是最短路径)。在权衡路线规划质量和执行效率的情况下,我们只需要寻求一个次优解就足够了。那如何快速找出一条接近于最短路线的次优路线呢?

这个快速的路径规划算法,就是我们这篇文章要讲的 A 算法*。实际上,A* 算法是对 Dijkstra 算法的优化和改造。如何将 Dijkstra 算法改造成 A* 算法呢?我们逐步来看一下。如果你对 Dijkstra 算法 不熟悉,可以先跳转到我上面链接的文章中看一下。

Dijkstra 算法其实是有点类似 BFS 算法 ,它每次找到跟起点最近的顶点,往外扩展。这种扩展的思路,其实有些盲目。为什么盲目呢?我用下图的例子来说明。假设下图对应着一个真实的地图,每个顶点在图中的位置,我们用一个二维坐标 (x, y) 来表示,其中 x,y 分别表示横坐标和纵坐标。


在 Dijkstra 算法的实现思路中,我们用一个优先级队列,来记录已经遍历到的顶点以及这个顶点与起点的路径长度。顶点与起点路径长度越小,就越先被从优先级队列中取出来扩展,从图中举的例子可以看出,尽管我们找的是从 s 到 t 的路线,但是最先被搜索到的顶点依次是 1,2,3。通过肉眼来观察,这个搜索方向跟我们期望的路线方向(s 到 t 是自西向东)是反着的,路线搜索到的方向明显跑偏了。

之所以会跑偏,是因为我们是按照顶点与起点的路径长度的大小,来安排出队列顺序的。与起点越近的顶点,就会越早出队列。我们并没有考虑到这个顶点到终点的距离,所以,在地图中,尽管 1,2,3 三个顶点离起始顶点最近,但离终点却越来越远。

那么,如果我们综合更多的因素,把这个顶点到终点可能还要走多远,也考虑进去,综合来判断哪个顶点该先出队列,那是不是就可以避免跑偏呢?

当我们遍历到某个顶点的时候,从起点走到这个顶点的路径长度是确定的,我们记作 g(i) (i 表示顶点编号)。但是,从这个顶点到终点的路径长度,我们是未知的。虽然确切的值无法提前知道,但是我们可以用其他估计值来替代。

这里我们可以通过这个顶点跟终点之间的直线距离,也就是欧几里得距离,来近似地估计这个顶点跟终点的路径长度(直线距离和路径长度不是一个概念)。我们把这个距离记作 h(i)(i 表示顶点编号),专业的叫法叫是启发函数(heuristic function)。因为欧几里得距离的计算公式,会涉及到比较耗时的开根号计算,所以我们一般用另外一种更加简单的计算距离的公式,叫作曼哈顿距离(Manhattan distance)。曼哈顿距离是两点之间横纵坐标的距离之和,计算的过程只涉及加减法和符号位反转,所以比欧几里得距离更加高效。

 private int hManhattan(Vertex v1, Vertex v2) {return Math.abs(v1.x - v2.x) + Math.abs(v1.y - v2.y);}

原来只是单纯地通过顶点与起点之间的路径长度 g(i),来判断谁先出队列,现在有了顶点到终点的路径长度估计值,我们通过两者之和 f(i) = g(i) + h(i),来判断哪个顶点该最先出队列。综合两部分,我们就能有效避免前面说的跑偏。这里 f(i) 的专业叫法叫做 估价函数(evaluation function)

经过上面的描述,我们可以发现,A* 算法就是对 Dijkstra 算法的简单改造。实际上,在代码实现上,我们也只需要改动几个地方就可以了。

在 A* 算法的代码实现中,顶点 Vertex 类的定义,跟 Dijkstra 算法中的定义,稍微有点区别,多了 x, y 坐标,以及刚刚提到的 f(i) 值。图 Graph 类的定义跟 Dijkstra 算法中的定义一样。我还是将完整代码贴出来,具体的讲解大家可以看我的将 Dijkstra 算法的文章—— Dijkstra 算法 。

首先是 Vertex 顶点类

package com.tyz.astar.core;/*** 构造一个顶点* @author Tong*/
public class Vertex {int id; //顶点编号int distance; //从起始点到这个顶点的距离int f; //f(i) = g(i) + h(i)int x; //顶点在地图中的横坐标int y; //顶点在地图中的纵坐标Vertex(int id, int x, int y) {this.id = id;this.x = x;this.y = y;this.f = Integer.MAX_VALUE;this.distance = Integer.MAX_VALUE;}}

边的构造

package com.tyz.astar.core;/*** 构造边* @author Tong*/
public class Edge {private int start;   //边的起始顶点编号private int end;  //边的终止顶点编号private int weight;   //边的权重public Edge() {}public Edge(int start, int end, int weight) {this.start = start;this.end = end;this.weight = weight;}public int getStart() {return start;}public void setStart(int start) {this.start = start;}public int getEnd() {return end;}public void setEnd(int end) {this.end = end;}public int getWeight() {return weight;}public void setWeight(int weight) {this.weight = weight;}}

优先级队列(小顶堆)

package com.tyz.astar.core;/*** 实现一个优先级队列(小顶堆)* @author Tong*/
class PriorityQueue {private Vertex[] nodes;private int count;public PriorityQueue(int vertex) {this.nodes = new Vertex[vertex+1];this.count = 0;}/*** 队首元素出队列* @return 队首元素*/Vertex poll() {Vertex vertex = this.nodes[1];this.nodes[1] = this.nodes[this.count--];heapifyUpToDown(1);return vertex;}/*** 添加元素并按优先级堆化* @param vertex*/void add(Vertex vertex) {this.nodes[++this.count] = vertex;vertex.id = this.count;heapifyDownToUp(count);}/*** 更新队列中元素的distance值* @param vertex*/void update(Vertex vertex) {this.nodes[vertex.id].distance = vertex.distance;heapifyDownToUp(vertex.id);}boolean isEmpty() {return this.count == 0;}void clear() {this.count = 0;}/*** 自上而下堆化* @param index*/private void heapifyUpToDown(int index) {while (index <= this.count) {int maxPos = index;if (index * 2 <= this.count && this.nodes[maxPos].distance > this.nodes[index*2].distance) {maxPos = 2 * index;} else if ((index * 2 + 1) <= count &&this.nodes[maxPos].distance > this.nodes[index*2+1].distance) {maxPos = index * 2 + 1;} else if (maxPos == index) {break;}swap(index, maxPos);index = maxPos;}}/*** 自下而上堆化* @param index*/private void heapifyDownToUp(int index) {while (index / 2 > 0 && this.nodes[index].distance < this.nodes[index / 2].distance) {swap(index, index / 2);index /= 2;}}/*** 交换两个元素对应的下标的值* @param index* @param maxPos*/private void swap(int index, int maxPos) {this.nodes[index].id = maxPos; //下标交换记录this.nodes[maxPos].id = index;Vertex temp = this.nodes[index];this.nodes[index] = this.nodes[maxPos];this.nodes[maxPos] = temp;}
}

A* 算法的代码实现和 Dijkstra 算法的代码实现,主要有三点区别:

  • 优先级队列构建的方式不同。A* 算法是根据 f 值(即 f(i) = g(i) + h(i) ),来构建优先级队列,而 Dijkstra 算法是根据 distance 值(也就是 g(i) )来构建优先级队列;
  • A* 算法在更新顶点 distance 值的时候,会同步更新 f 值;
  • 循环结束的条件也不一样, Dijkstra 算法是在终点出队列的时候才结束,A* 算法是一旦遍历到终点就结束。

代码实现如下

【数据结构与算法】->算法-> A* 搜索算法->如何实现游戏中的寻路功能?相关推荐

  1. java 寻路算法_游戏中的寻路算法解析

    游戏角色的自动寻路,已经是游戏中一个历史比较悠久的领域,较为成熟也有很多种实现.这里摘录一句后面所提的参考资料中的描述:"业内AI开发者中有一句话:"寻路已不是问题."我 ...

  2. 那些游戏中的寻路算法

    在游戏中,AI人物的移动往往有许多种实现方法,本文主要列出其中的几种常见的2D寻路方法并附上完整源代码,供读者参考,批评以及指正. 所有的代码均在Unity下完成,并通过测试可以使用. Depth-F ...

  3. unity 游戏中的寻路与导航系统(5种寻路算法详解)

    @了解游戏中常见的寻路方式 通常来讲一般是根据建模方式来决定寻路方式 常见的寻路方式--建模方式 这里提供一下三种建模方式的示意图,如下 ,分别对应着,原图.Grid.WayPoint.NavMesh ...

  4. 程序员的算法趣题Q58: 丢手绢游戏中的总移动距离

    1. 问题描述 2. 解题分析 搜索最短距离,图搜索问题中的最短距离问题,可以用广度优先搜索策略来解决. 2.1 搜索树示意图 搜索树示意图如下: 2.2 算法流程 用一维数组表示当前状态,但是要注意 ...

  5. 游戏中的AI算法总结与改进

    一.人工智能的定义 人工智能(AI,Artificial Intelligent),指的是通过算法编程使计算机模仿人完成一些像人一样的任务,同时在执行任务时模仿人的思维和智慧,甚至通过大量学习训练积累 ...

  6. 盘点即时战略游戏中高实用性寻路算法

    编者按:在即时战略(RTS)游戏中,寻路系统可谓其核心要素之一,但它的算法也是较难攻克的内容之一.在这篇文章中,来自iSamurai工作室的伍一峰为广大游戏开发者带来了他对即时战略游戏寻路算法的深入思 ...

  7. 游戏编程中的寻路算法研究

    近年来,游戏产业的快速发展带动了游戏中人工 智能(Artificial Intelligence,简称AI)的发展,越来越 多的游戏采用人工智能技术提高游戏的可玩性.在电 子游戏中,玩家操控主要角色, ...

  8. [转] 游戏编程中的寻路算法研究

    [url]http://blog.csdn.net/ityuany/archive/2010/04/21/5509750.aspx[/url] 近年来,游戏产业的快速发展带动了游戏中人工 智能(Art ...

  9. python数据结构推荐书-「算法与数据结构」从入门到进阶吐血整理推荐书单

    推荐一下「算法与数据结构」从入门到进阶的书单. 一.入门系列 这些书籍通过图片.打比方等通俗易懂的方法来讲述,让你能达到懂一些基础算法,线性表,堆栈,队列,树,图,DP算法,背包问题等,不要求会实现, ...

最新文章

  1. 将图片保存到系统相冊的两种方法
  2. 定理在数学中的简写形式_西方把勾股定理叫毕达哥拉斯定理,我们的教材上是不是该改改名?...
  3. 开源免费的.NET图像即时处理的组件ImageProcessor
  4. MySQL基于日志还原数据
  5. 假设系统中共有5个{P0,P1,P2,P3,P4}和A,B,C三类资源;A类资源共有10个,B类资源共有5个,C类资源共有7个。在时刻T0,系统资源分配情况如下表8-14所示。
  6. Kotlin 常用API汇总
  7. PLC编程的要求有哪些?
  8. python机器学习算法.mobi_推荐《scikit-learn机器学习常用算法原理及编程实战》PDF版+epub版+源代码...
  9. Process finished with exit code -1073741819(0x0000005)
  10. 饥荒独立服务器在线模式收不到,Windows 服务器搭建Don’t Starve Together饥荒独立服务器教程...
  11. maskrcnn selected_polygons.append(self.polygons[i]) IndexError: list index out of range
  12. Java基础(32)
  13. python 判断是否有某个属性_python如何判断对象的某个属性
  14. 理解 LDA 主题模型
  15. Ubuntu 16.04 安装 CUDA Toolkit 10.0
  16. SQL SERVER 查询、删除重复数据
  17. Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
  18. 计算机科学的重要意义,论文开题报告计算机科学与技术的现代化运用,理论意义和现实意义,对现代社会的重要性,为什么要研究这个...
  19. python练习—简单公式计算
  20. 类似余额宝数值增加的动画

热门文章

  1. Centos7使用yum更新gcc----依赖centos-release-scl源
  2. Android am与pm命令详解
  3. 桌面计算机怎么设置时钟同步,电脑显示时间怎么设置
  4. 彻底搞懂Netty高性能之零拷贝
  5. 知识星球《玩转股票量化交易》精华内容概览-2023扬帆起航
  6. 访问学者在新加坡访学有哪些特色小吃?
  7. DRE Viewset(视图集)的使用
  8. 4.2.1 模糊理论
  9. Express+MongoDB服务端开发教程
  10. 【NB-Iot自我学习之路_3】NB平台介绍【电信篇】+【移动篇】