(转载)A星+(最优路径算法)

转帖:A*寻路算法介绍

介绍

你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢?
如果是的话,请看这篇教程,我们会展示如何使用A寻路算法来实现它!
在网上已经有很多篇关于A
寻路算法的文章,但是大部分都是提供给已经解基本原理的高级开发者的。
本篇教程将从最基本的原理讲起。我们会一步步讲解A寻路算法,幷配有很多图解和例子。
不管你使用的是什么编程语言或者操作平台,你会发现本篇教程很有帮助,因为它在非编程语言的层面上解释算法的原理。稍后,会有一篇教程,展示如何在Cocos2D iPhone 游戏中实现A
算法。
现在找下到达一杯咖啡因饮料和美味的零食的最短路径,开始吧!

一只探路猫

让我们想象一下,有一款游戏,游戏中一只猫想要找到获取骨头的路线。
“为什么会有一只猫想要骨头?!”你可能会这么想。在本游戏中, 这是一只狡猾的猫,他想捡起骨头给狗,以防止被咬死!
现在想像一下下图中的猫想找到到达骨头的最短路径。

不幸的是,猫不能直接从它当前的位置走到骨头的位置,因为有面墙挡住去路,而且它在游戏中不是一只幽灵猫!
游戏中的猫同样懒惰,它总是想找到最短路径,这样当他回家看望它的女朋友时不会太累。
但是我们如何编写一个算法计算出猫要选择的那条路径呢?A*算法拯救我们!

简化搜索区域

寻路的第一步是简化成容易控制的搜索区域。
怎么处理要根据游戏来决定。例如,我们可以将搜索区域划分成像素点,但是这样的划分粒度对于我们这款基于方块的游戏来说太高(没必要)。
作为代替,我们使用方块(一个正方形)作为寻路算法的单元。其他的形状类型也是可能的(比如三角形或者六边形),但是正方形是最简单并且最适合我们需求的。
像那样去划分,我们的搜索区域可以简单的用一个地图大小的二维数组去表示。所以如果是25*25方块大小的地图,我们的搜索区域将会是一个有625个正方形的数组。如果我们把地图划分成像素点,搜索区域就是一个有640,000个正方形的数组(一个方块是32*32像素)!
现在让我们基于目前的区域,把区域划分成多个方块来代表搜索空间(在这个简单的例子中,7*6个方块 = 42 个方块)。

Open和Closed列表

既然我们创建一个简单的搜索区域,我们来讨论下A*算法的工作原理吧。
除了懒惰之外,我们的猫没有好的记忆力,所以它需要两个列表。

  • 一个记录下所有被考虑来寻找最短路径的方块(称为open列表)
  • 一个记录下不会再被考虑的方块(成为closed列表,称为:路径,也就是close列表存路径)

猫首先在closed列表中添加当前位置(我们把这个开始点称为点“A”)。然后,把所有与它当前位置相邻的可通行小方块添加到open列表中。
下图是猫在某一位置时的情景(绿色代表open列表)。

现在猫需要判断在这些选项中,哪项才是最短路径,但是它要如何去选择呢?
在A*寻路算法中,通过给每一个方块一个和值,该值被称为路径增量。让我们看下它的工作原理!

路径增量

我们将会给每个方块一个G+H和值。

  • G是从开始点A到当前方块的移动量。所以从开始点A到相邻小方块的移动量为1,该值会随着离开始点越来越远而增大。
  • H是从当前方块到目标点(我们把它称为点B,代表骨头!)的移动量估算值。这个常被称为探视,因为我们不确定移动量是多少 – 仅仅是一个估算值。

你也许会对“移动量”感兴趣。在游戏中,这个概念很简单 – 仅仅是方块的数量。
然而,在游戏中你可以对这个值做调整。例如。

  • 如果你允许对角线移动,你可以针对对角线移动把移动量调得大一点。
  • 如果你有不同的地形,你可以将相应的移动量调整得大一点 – 例如针对一块沼泽,水,或者猫女海报:-)

这就是大概的意思 – 现在让我们详细分析下如何计算出G和H值。

关于G值

G是从开始点A到达当前方块的移动量(在本游戏中是指方块的数目)。
为计算出G的值,我们需要从它的前继(上一个方块)获取,然后加1。所以,每个方块的G值代表从点A到该方块所形成路径的总移动量。
例如,下图展示两条到达不同骨头的路径,每个方块都标有它的G值。

关于H值

H值是从当前方块到终点的移动量估算值(在本游戏中是指方块的数目)。
移动量估算值离真实值越接近,最终的路径会更加精确。如果估算值停止作用,很可能生成出来的路径不会是最短的(但是它可能是接近的)。这个题目相对复杂,所以我们不会再本教程中讲解,但是我在教程的末尾提供一个网络链接,对它做很好的解释。
为让它更简单,我们将使用“曼哈顿距离方法”(也叫“曼哈顿长”或者“城市街区距离”),它只是计算出距离点B,剩下的水平和垂直的方块数量,略去障碍物或者不同陆地类型的数量。
例如,下图展示使用“城市街区距离”,从不同的开始点到终点,去估算H的值(黑色字)。

A星算法

既然你知道如何计算每个方块的和值(我们将它称为F,等于G+H), 我们来看下A*算法的原理。
猫会重复以下步骤来找到最短路径。

  1. 将方块添加到open列表中,该列表有最小的和值。且将这个方块称为S吧。
  2. 将S从open列表移除,然后添加S到closed列表中。
  3. 对于与S相邻的每一块可通行的方块T:
    • 如果T在closed列表中:不管它。
    • 如果T不在open列表中:添加它然后计算出它的和值。
    • 如果T已经在open列表中:当我们使用当前生成的路径到达那里时,检查F和值是否更小。如果是,更新它的和值和它的前继。

如果你对它的工作原理还有点疑惑,不用担心 – 我们会用例子一步步介绍它的原理!

猫的路径

让我们看下我们的懒猫到达骨头的行程例子。
在下图中,我根据以下内容,列出了公式F = G + H中的每项值。

  • F(方块的和值):左上角
  • G(从A点到方块的移动量):左下角
  • H(从方块到B点的估算移动量): 右下角

同时,箭头指示了到达相应方块的移动方向。
最后,在每一步中,红色方块表示closed列表,绿色方块表示open列表。
好的,我们开始吧!

第一步

第一步,猫会确定相对于开始位置(点A)的相邻方块,计算出他们的F和值,然后把他们添加到open列表中。

你会看到每个方块都列出H值(有两个是6,一个是4)。我建议根据“城市街区距离”去计算方块的相关值,确保你理解它的原理。
同时注意F值(在左上角)是G(左下角)值和H(右下脚)值的和。

第二步

在第二步中,猫选择F和值最小的方块,把它添加到closed列表中,然后检索它的相邻方块的相关数值。

现在你将看到拥有最小增量的是F值为5的方块。猫尝试添加所有相邻的方块到open列表中(然后计算他们的和值),除猫自身的方块不能添加以外(因为它已经被添加到closed列表中)或者它是墙壁方块(因为它不能通行)。
注意被添加到open列表的两个新方块,他们的G值都增加1,因为他们现在离开始点有2个方块远。你也许需要再计算下“城市街区距离”以确保你理解每个新方块的H值。

第三步

再次,我们选择有最小F和值(5)的方块,继续重复之前的步骤。

现在,只有一个可能的方块被添加到open列表中,因为已经有一个相邻的方块在close列表中,其他两个是墙壁方块。

第四步

现在我们遇到一个有趣的情况。正如你之前看到的,有4个方块的F和值都为7 – 我们要怎么做呢?!
有几种解决方法可以使用,但是最简单(快速)的方法是一直跟着最近被添加到open列表中的方块。(F相同的时候,取最新的F值路径。因为之前的F值都相互比较过)现在继续沿着最近被添加的方块前进。

这次有两个可通过的相邻方块,我们还是像之前那样计算他们的和值。

第五步

接着我们选择最小和值(7)的方块,继续重复之前的步骤。

我们越来越接近终点!

第六步

你现在训练有素!我打赌你能够猜出下一步是下面这样子。

我们差不多到终点,但是这次你看到有两条到达骨头的最短路径提供给我们选择。

在我们的例子中,有两条最短路径。

  • 1-2-3-4-5-6
  • 1-2-3-4-5-7

It doesn’t really matter which of these we choose, it comes down to the actual implementation in code.
选择哪一条其实没关系,现在到真正用代码实现的时候。

第七步

让我们从其中一块方块,再重复一遍步骤吧。

骨头在open列表中!

第八步

现在目标方块在open列表中,算法会把它添加到closed列表中。

然后,算法要做的所有事情就是返回,计算出最终的路径!

一只有远见的猫

在上面的例子中,我们看到当猫在寻找最短路径时,它经常选择更好的方块(那个在它的未来最短路径上的方块)- 好像它是一只有远见的猫!
但是如果猫是盲目的,并且总是选择第一个添加到它的列表上的方块,会发生什么事情?
下图展示所有在寻找过程中会被使用到的方块。你会看到猫在尝试更多的方块,但是它仍然找到最短路径(不是之前的那条,而是另一条等价的)。

图中的红色方块不代表最短路径,它们只是代表在某个时候被选择为“S”的方块。
我建议你看着上面的图,并且尝试过一遍步骤。这次无论你看到哪个相邻的方块,都选择“最坏”的方式去走。你会发现最后还是找到最短路径!
所以你可以看到跟随一个“错误的”方块是没有问题的,你仍然会在多次重复尝试后找到最短路径。
所以在我们的实现中,我们会按照以下的算法添加方块到open列表中。

  • 相邻的方块会返回这些顺序: 上面/左边/下面/右边。
  • 当所有的方块都有相同的和值后,方块会被添加到open列表中(所以第一个被添加的方块是第一个被猫挑选的)。

下面是从原路返回的示意图。

最短的路径是从终点开始,一步步返回到起点构成的(例子:在终点我们可以看到箭头指向右边,所以该方块的前继在它的左边)。
总的来说,我们可以用下面的伪代码,合成猫的寻找过程。
这是Objective-C写的,但是你可以用任何的语言去实现它。

object-c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[openList add:originalSquare]; // start by adding the original position to the open list
do {
currentSquare = [openList squareWithLowestFScore]; // Get the square with the lowest F score

[closedList add:currentSquare]; // add the current square to the closed list
[openList remove:currentSquare]; // remove it to the open list

if ([closedList contains:destinationSquare]) { // if we added the destination to the closed list, we've found a path
// PATH FOUND
break; // break the loop
}

adjacentSquares = [currentSquare walkableAdjacentSquares]; // Retrieve all its walkable adjacent squares

foreach (aSquare in adjacentSquares) {

if ([closedList contains:aSquare]) { // if this adjacent square is already in the closed list ignore it
continue; // Go to the next adjacent square
}

if (![openList contains:aSquare]) { // if its not in the open list

// compute its score, set the parent
[openList add:aSquare]; // and add it to the open list

} else { // if its already in the open list

// test if using the current G score make the aSquare F score lower, if yes update the parent because it means its a better path

}
}

} while(![openList isEmpty]); // Continue until there is no more available square in the open list (which means there is no path)

java

上下左右寻找,未做斜对角线寻找路径

Node.java节点类,存x,y,parent(原路返回的节点),计算F的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package demo1;

/**
* 节点
*
*/
public class Node {
//父节点,只是寻找回去的路径
public Node parent;

public Node(int x, int y) {
this.x = x;
this.y = y;
}

public int x;
public int y;

public int F;
public int G;
public int H;

public void calcF() {
this.F = this.G + this.H;
}

}

StarA.java寻最短路径核心算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package demo1;

import java.util.ArrayList;
import java.util.List;

public class StarA {

public static final int[][] NODES = {
{ 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 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, 0, 0 },
};

/*{ 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 2, 2, 1, 0, 2, 0, 0, 0 },
{ 0, 0, 2, 1, 0, 2, 0, 0, 0 },
{ 0, 0, 2, 2, 2, 2, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */

public static final int STEP = 10;

private ArrayList<Node> openList = new ArrayList<Node>();
private ArrayList<Node> closeList = new ArrayList<Node>();

/**
* 查最短F节点在OpenList中
* @return
*/
public Node findMinFNodeInOpneList() {
//假设第一个点,F值最小
Node tempNode = openList.get(0);
for (Node node : openList) {
if (node.F <= tempNode.F) {
tempNode = node;
}
}
return tempNode;
}

/**
* 查相邻的节点
* @param currentNode 当前的节点
* @return
*/
public ArrayList<Node> findNeighborNodes(Node currentNode) {
ArrayList<Node> arrayList = new ArrayList<Node>();
// 只考虑上下左右,不考虑斜对角
int topX = currentNode.x;
int topY = currentNode.y - 1;
//在面板范围内
if (canReach(topX, topY) && !exists(closeList, topX, topY)) {
arrayList.add(new Node(topX, topY));
}
int bottomX = currentNode.x;
int bottomY = currentNode.y + 1;
if (canReach(bottomX, bottomY) && !exists(closeList, bottomX, bottomY)) {
arrayList.add(new Node(bottomX, bottomY));
}
int leftX = currentNode.x - 1;
int leftY = currentNode.y;
if (canReach(leftX, leftY) && !exists(closeList, leftX, leftY)) {
arrayList.add(new Node(leftX, leftY));
}
int rightX = currentNode.x + 1;
int rightY = currentNode.y;
if (canReach(rightX, rightY) && !exists(closeList, rightX, rightY)) {
arrayList.add(new Node(rightX, rightY));
}
return arrayList;
}

/**
* 是否能到达
* @param x
* @param y
* @return
*/
public boolean canReach(int x, int y) {
if (x >= 0 && x < NODES.length && y >= 0 && y < NODES[0].length) {
return NODES[x][y] == 0;
}
return false;
}

/**
* 查到路径
* @param startNode 开始位置
* @param endNode 结束位置
* @return
*/
public Node findPath(Node startNode, Node endNode) {

// 把起点加入 open list
openList.add(startNode);

while (openList.size() > 0) {
// 遍历 open list ,查找 F值最小的节点,把它作为当前要处理的节点
Node currentNode = findMinFNodeInOpneList();
// 从open list中移除
openList.remove(currentNode);
// 把这个节点移到 close list,路径节点
closeList.add(currentNode);
//周围节点
ArrayList<Node> neighborNodes = findNeighborNodes(currentNode);
for (Node node : neighborNodes) {
//存在openlist中,是待考虑的路径
if (exists(openList, node)) {
//重新计算g值,重新赋值
foundPoint(currentNode, node);
} else {//openlist不存在,添加到openlist中
notFoundPoint(currentNode, endNode, node);
}
}
//查询节点是否在待考虑的openlist中
if (find(openList, endNode) != null) {
return find(openList, endNode);
}
}

return find(openList, endNode);
}

/**
* 查节点,计算g值,g值小的重新计算F值
* @param tempStart
* @param node
*/
private void foundPoint(Node tempStart, Node node) {
int G = calcG(tempStart, node);
if (G < node.G) {
node.parent = tempStart;
node.G = G;
node.calcF();
}
}

/**
*
* @param tempStart 父节点
* @param end 最终位置节点
* @param node 当前位置的节点
*/
private void notFoundPoint(Node tempStart, Node end, Node node) {
node.parent = tempStart;
node.G = calcG(tempStart, node);
node.H = calcH(end, node);
node.calcF();
openList.add(node);
}

/**
* G是从开始点A到达当前方块的移动量
* 计算G
* @param start
* @param node
* @return
*/
private int calcG(Node start, Node node) {
int G = STEP;
int parentG = node.parent != null ? node.parent.G : 0;
return G + parentG;
}

/**
* H值是从当前方块到终点的移动量估算值
* @param end 结束节点位置
* @param node 当前位置
* @return
*/
private int calcH(Node end, Node node) {
int step = Math.abs(node.x - end.x) + Math.abs(node.y - end.y);
return step * STEP;
}

/**
* 查询节点在list中
* @param nodes
* @param point
* @return
*/
public static Node find(List<Node> nodes, Node point) {
for (Node n : nodes)
if ((n.x == point.x) && (n.y == point.y)) {
return n;
}
return null;
}

/**
* 节点是否在list中
* @param nodes
* @param node
* @return
*/
public static boolean exists(List<Node> nodes, Node node) {
for (Node n : nodes) {
if ((n.x == node.x) && (n.y == node.y)) {
return true;
}
}
return false;
}

/**
* 节点是否在list中
* @param nodes
* @param x
* @param y
* @return
*/
public static boolean exists(List<Node> nodes, int x, int y) {
for (Node n : nodes) {
if ((n.x == x) && (n.y == y)) {
return true;
}
}
return false;
}

public static void main(String[] args) {
Node startNode = new Node(5, 1);
Node endNode = new Node(5, 5);
Node parent = new StarA().findPath(startNode, endNode);

for (int i = 0; i < NODES.length; i++) {
for (int j = 0; j < NODES[0].length; j++) {
System.out.print(NODES[i][j] + ", ");
}
System.out.println();
}

//添加所有的路径
ArrayList<Node> arrayList = new ArrayList<Node>();
while (parent != null) {
// System.out.println(parent.x + ", " + parent.y);
arrayList.add(new Node(parent.x, parent.y));
parent = parent.parent;
}
System.out.println("\n");

for (int i = 0; i < NODES.length; i++) {
for (int j = 0; j < NODES[0].length; j++) {
if (exists(arrayList, i, j)) {
System.out.print("2, ");
} else {
System.out.print(NODES[i][j] + ", ");
}

}
System.out.println();
}

}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
原地址
0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 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, 0, 0,

最短路径
0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 2, 2, 1, 0, 2, 0, 0, 0,
0, 0, 2, 1, 0, 2, 0, 0, 0,
0, 0, 2, 2, 2, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,

上下左右,斜对角线寻找

Node2.java节点类,存x,y,parent(原路返回的节点),比较F的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package demo2;

import java.util.Comparator;

/**
* 节点类
*
*/
class Node2 {
private int x;// X坐标
private int y;// Y坐标
private Node2 parentNode;// 父类节点
private int g;// 当前点到起点的移动耗费
private int h;// 当前点到终点的移动耗费,即曼哈顿距离|x1-x2|+|y1-y2|(忽略障碍物)
private int f;// f=g+h

public Node2(int x, int y, Node2 parentNode) {
this.x = x;
this.y = y;
this.parentNode = parentNode;
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public Node2 getParentNode() {
return parentNode;
}

public void setParentNode(Node2 parentNode) {
this.parentNode = parentNode;
}

public int getG() {
return g;
}

public void setG(int g) {
this.g = g;
}

public int getH() {
return h;
}

public void setH(int h) {
this.h = h;
}

public int getF() {
return f;
}

public void setF(int f) {
this.f = f;
}
}

// 节点比较类
class NodeFComparator implements Comparator<Node2> {

@Override
public int compare(Node2 o1, Node2 o2) {
return o1.getF() - o2.getF();
}
}

StarA2.java:寻最短路径核心算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package demo2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
*
* A星算法步骤:
* 1.起点先添加到开启列表中
* 2.开启列表中有节点的话,取出第一个节点,即最小F值的节点
* 判断此节点是否是目标点,是则找到了,跳出
* 根据此节点取得八个方向的节点,求出G,H,F值 判断每个节点在地图中是否能通过,不能通过则加入关闭列表中,跳出 判断每个节点是否在关闭列表中,在则跳出
* 判断每个节点是否在开启列表中,在则更新G值,F值,还更新其父节点;
* 不在则将其添加到开启列表中,计算G值,H值,F值,添加其节点
* 3.把此节点从开启列表中删除,再添加到关闭列表中
* 4.把开启列表中按照F值最小的节点进行排序,最小的F值在第一个
* 5.重复2,3,4步骤
* 直到目标点在开启列表中,即找到了;目标点不在开启列表中,开启列表为空,即没找到
*
*/
public class StarA2 {
private int[][] map;// 地图(1可通过 0不可通过)
private List<Node2> openList;// 开启列表
private List<Node2> closeList;// 关闭列表
private final int COST_STRAIGHT = 10;// 垂直方向或水平方向移动的路径评分
private final int COST_DIAGONAL = 14;// 斜方向移动的路径评分
private int row;// 行
private int column;// 列

public StarA2(int[][] map, int row, int column) {
this.map = map;
this.row = row;
this.column = column;
openList = new ArrayList<Node2>();
closeList = new ArrayList<Node2>();
}

/**
* 查找坐标(-1:错误,0:没找到,1:找到)
*
* @param x1 起始位置X
* @param y1 起始位置Y
* @param x2 结束位置X
* @param y2 结束位置Y
* @return (-1:错误,0:没找到,1:找到)
*/
public int search(int x1, int y1, int x2, int y2) {
if (x1 < 0 || x1 >= row || x2 < 0 || x2 >= row || y1 < 0 || y1 >= column || y2 < 0 || y2 >= column) {
return -1;
}
if (map[x1][y1] == 0 || map[x2][y2] == 0) {
return -1;
}
Node2 sNode = new Node2(x1, y1, null);
Node2 eNode = new Node2(x2, y2, null);
openList.add(sNode);
List<Node2> resultList = search(sNode, eNode);
// 没有找到
if (resultList.size() == 0) {
return 0;
}

// 遍历结果,x,y赋值
for (Node2 node : resultList) {
map[node.getX()][node.getY()] = -1;
}
// 找到
return 1;
}

/**
* 查找核心算法
*
* @param sNode
* @param eNode 目标节点
* @return
*/
private List<Node2> search(Node2 sNode, Node2 eNode) {
List<Node2> resultList = new ArrayList<Node2>();
boolean isFind = false;
Node2 node = null;
while (openList.size() > 0) {
// 取出开启列表中最低F值,即第一个存储的值的F为最低的
// 有可能是目标节点的上一个节点
node = openList.get(0);
// 判断是否找到目标点
if (node.getX() == eNode.getX() && node.getY() == eNode.getY()) {
isFind = true;
break;
}
// 上
if ((node.getY() - 1) >= 0) {
checkPath(node.getX(), node.getY() - 1, node, eNode, COST_STRAIGHT);
}
// 下
if ((node.getY() + 1) < column) {
checkPath(node.getX(), node.getY() + 1, node, eNode, COST_STRAIGHT);
}
// 左
if ((node.getX() - 1) >= 0) {
checkPath(node.getX() - 1, node.getY(), node, eNode, COST_STRAIGHT);
}
// 右
if ((node.getX() + 1) < row) {
checkPath(node.getX() + 1, node.getY(), node, eNode, COST_STRAIGHT);
}
// 左上
if ((node.getX() - 1) >= 0 && (node.getY() - 1) >= 0) {
checkPath(node.getX() - 1, node.getY() - 1, node, eNode, COST_DIAGONAL);
}
// 左下
if ((node.getX() - 1) >= 0 && (node.getY() + 1) < column) {
checkPath(node.getX() - 1, node.getY() + 1, node, eNode, COST_DIAGONAL);
}
// 右上
if ((node.getX() + 1) < row && (node.getY() - 1) >= 0) {
checkPath(node.getX() + 1, node.getY() - 1, node, eNode, COST_DIAGONAL);
}
// 右下
if ((node.getX() + 1) < row && (node.getY() + 1) < column) {
checkPath(node.getX() + 1, node.getY() + 1, node, eNode, COST_DIAGONAL);
}
// 从开启列表中删除
// 添加到关闭列表中
closeList.add(openList.remove(0));
// 开启列表中排序,把F值最低的放到最底端
Collections.sort(openList, new NodeFComparator());
}
//是否结束
if (isFind) {
getPath(resultList, node);
}
return resultList;
}

/**
* 查询此路是否能走通(节点是否可以达到)
* @param x
* @param y
* @param parentNode 父节点
* @param eNode 目标节点
* @param cost
* @return
*/
private boolean checkPath(int x, int y, Node2 parentNode, Node2 eNode, int cost) {
Node2 node = new Node2(x, y, parentNode);
// 查找地图中是否能通过
//0:可以到达
if (map[x][y] == 0) {
closeList.add(node);
return false;
}
// 查找关闭列表中是否存在
//在closeList中,则不可达
if (isListContains(closeList, x, y) != -1) {
return false;
}
// 查找开启列表中是否存在
int index = -1;
//在openlist中存在
if ((index = isListContains(openList, x, y)) != -1) {
// G值是否更小,即是否更新G,F值
// 重新设置节点G,F值
if ((parentNode.getG() + cost) < openList.get(index).getG()) {
node.setParentNode(parentNode);
countG(node, eNode, cost);
countF(node);
openList.set(index, node);
}
} else {//节点不再openlist中
// 添加到开启列表中
node.setParentNode(parentNode);
//计算G,H,F值
count(node, eNode, cost);
openList.add(node);
}
return true;
}

/**
* 集合中是否包含某个元素(-1:没有找到,否则返回所在的索引)
* @param list
* @param x
* @param y
* @return
*/
private int isListContains(List<Node2> list, int x, int y) {
for (int i = 0; i < list.size(); i++) {
Node2 node = list.get(i);
if (node.getX() == x && node.getY() == y) {
return i;
}
}
return -1;
}

/**
* 从终点往返回到起点(原路返回)
* 递归
* @param resultList
* @param node
*/
private void getPath(List<Node2> resultList, Node2 node) {
if (node.getParentNode() != null) {
getPath(resultList, node.getParentNode());
}
resultList.add(node);
}

/**
* 计算G,H,F值
* @param node
* @param eNode
* @param cost
*/
private void count(Node2 node, Node2 eNode, int cost) {
countG(node, eNode, cost);
countH(node, eNode);
countF(eNode);
}

/**
* 计算G值(从A点到方块的移动量)
* @param node
* @param eNode
* @param cost
*/
private void countG(Node2 node, Node2 eNode, int cost) {
//父节点是null,直接设置G值
if (node.getParentNode() == null) {
node.setG(cost);
} else {
node.setG(node.getParentNode().getG() + cost);
}
}

/**
* 计算H值(从方块到B点的估算移动量)
* @param node
* @param eNode
*/
private void countH(Node2 node, Node2 eNode) {
node.setF(Math.abs(node.getX() - eNode.getX()) + Math.abs(node.getY() - eNode.getY()));
}

/**
* 计算F值
* @param node
*/
private void countF(Node2 node) {
node.setF(node.getG() + node.getH());
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package demo2;

public class TestStarA2 {
static int[][] map=new int[][]{// 地图数组
{1,1,1,1,1,1,1,1,1,1},
{1,1,1,1,0,1,1,1,1,1},
{1,1,1,1,0,1,1,1,1,1},
{1,1,1,1,0,1,1,1,1,1},
{1,1,1,1,0,1,1,1,1,1},
{1,1,1,1,0,1,1,1,1,1}
};

public static void main(String[] args) {

StarA2 aStar = new StarA2(map, 6, 10);
int flag = aStar.search(3, 0, 3, 8);
if (flag == -1) {
System.out.println("传输数据有误!");
} else if (flag == 0) {
System.out.println("没找到!");
} else {// 找到
//遍历行
for (int x = 0; x < 6; x++) {
for (int y = 0; y < 10; y++) {
if (map[x][y] == 1) {
System.out.print("0");
} else if (map[x][y] == 0) {
System.out.print("1");
} else if (map[x][y] == -1) {
System.out.print("2");
}
}
System.out.println();
}
}
}
}

结果:

1
2
3
4
5
6
0222200000
2000120000
2000102000
2000100220
0000100000
0000100000

改进

优化速度

排序查找算法

顾名思义,这个算法就是,始终维持开启列表的排序,从小到大,或者从大到小,这样当我们查找最小值时,只需要把第一个节点取出来就行。
提高openList

二叉树查找算法

这个算法可以说是A*算法的黄金搭档,也是被称为苛求速度的binary heap的方法。
就是根据二叉树原理,来维持开启列表的“排序”,这里说的排序只是遵循二叉树的原理的排序而已,即父节点永远比子节点小,就像下面这样。

1
2
3
4
5
6
graph TD;
1-->5;
1-->9;
5-->7;
9-->12;
9-->10;

二叉树每个节点的父节点下标 = n / 2;(小数去掉)
二叉树每个节点的左子节点下标 = n 2;右子节点下标 = n 2 +1
注意,这里的下标和它的值是两个概念。
我们看到,耗时15毫秒,速度是这三个方法里最快的,但是因为这个数字是不够准确的,实际上,用二叉树查找法,会让A*算法的速度提高几倍到10几倍,在一些足够复杂的地图里,这个速度是成指数成长的。

总结

得出结论,用A*算法,就要配套的用它的黄金搭档,二叉树,它可以让你的游戏由完美走向更完美。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×