A*算法
问题
实现游戏地图中自动寻路功能
分析
可使用Dijkstra算法计算最短路径,让游戏角色按路径行走;
但Dijkstra算法是BFS的思想,会有盲目计算,效率低;
寻路不用非得走最短路径,可走次短即可
鉴于平衡效率与效果,使用优化版Dijkstra算法:A*算法
A*算法
优化版Dijkstra算法
启发式搜索
引入启发函数(heuristic function)来优化出队顺序
根据启发函数和Dijkstra算法顶点距离g(i),得到估价函数(evaluation function):f(i)=g(i)+h(i)
此例中的启发函数选择:
-
- 欧几里得距离:直线距离,但会涉及比较耗时的开根号计算
- 曼哈顿距离(Manhattan distance):两点之间横纵坐标的距离之和。计算的过程只涉及加减法、符号位反转,所以比欧几里得距离更加高效。
-
int hManhattan(Vertex v1, Vertex v2) { // Vertex表示顶点,后面有定义 return Math.abs(v1.x - v2.x) + Math.abs(v1.y - v2.y); }
实现
顶点 Vertex 类的定义,相比Dijkstra 算法中的定义,多了 x,y 坐标,以及估价值 f(i) 。
-
private class Vertex { public int id; // 顶点编号ID public int dist; // 从起始顶点,到这个顶点的距离,也就是g(i) public int f; // 新增:f(i)=g(i)+h(i) public int x, y; // 新增:顶点在地图中的坐标(x, y) public Vertex(int id, int x, int y) { this.id = id; this.x = x; this.y = y; this.f = Integer.MAX_VALUE; this.dist = Integer.MAX_VALUE; } } // Graph类的成员变量,在构造函数中初始化 Vertex[] vertexes = new Vertex[this.v]; // 新增一个方法,添加顶点的坐标 public void addVetex(int id, int x, int y) { vertexes[id] = new Vertex(id, x, y) }
A* 算法是对 Dijkstra 算法的简单改造,实际上稍微改动几行代码,就能把 Dijkstra 算法的代码实现,改成 A* 算法的代码实现,
相比 Dijkstra 算法的代码实现,主要有 3 点区别:
-
- 优先级队列构建的方式不同。A* 算法是根据 f 值来构建优先级队列,而 Dijkstra 算法是根据 g 值;
- A* 算法在更新顶点 dist 值的时候,会同步更新 f 值;
- 循环结束的条件也不一样。Dijkstra 算法是在终点出队列的时候才结束,A* 算法是一旦遍历到终点就结束。
-
public void astar(int s, int t) { // 从顶点s到顶点t的路径 int[] predecessor = new int[this.v]; // 用来还原路径 // 按照vertex的f值构建的小顶堆,而不是按照dist PriorityQueue queue = new PriorityQueue(this.v); boolean[] inqueue = new boolean[this.v]; // 标记是否进入过队列 vertexes[s].dist = 0; vertexes[s].f = 0; queue.add(vertexes[s]); inqueue[s] = true; while (!queue.isEmpty()) { Vertex minVertex = queue.poll(); // 取堆顶元素并删除 for (int i = 0; i < adj[minVertex.id].size(); ++i) { Edge e = adj[minVertex.id].get(i); // 取出一条minVetex相连的边 Vertex nextVertex = vertexes[e.tid]; // minVertex-->nextVertex if (minVertex.dist + e.w < nextVertex.dist) { // 更新next的dist,f nextVertex.dist = minVertex.dist + e.w; nextVertex.f = nextVertex.dist+hManhattan(nextVertex, vertexes[t]); predecessor[nextVertex.id] = minVertex.id; if (inqueue[nextVertex.id] == true) { queue.update(nextVertex); } else { queue.add(nextVertex); inqueue[nextVertex.id] = true; } } if (nextVertex.id == t) { // 只要到达t就可以结束while了 queue.clear(); // 清空queue,才能推出while循环 break; } } } // 输出路径 System.out.print(s); print(s, t, predecessor); // print函数请参看Dijkstra算法的实现 }