A*寻路

A*寻路算法是一种常用的路径规划算法,用于在图或者网格中找到最短路径。它基于启发式搜索,综合了Dijkstra算法和贪心算法的优点,通过估算当前节点到目标节点的代价,选择最合适的节点进行探索。

具体原理如下:

  1. 创建一个起点节点和一个目标节点。从起点开始探索,并将起点加入开放列表。

  2. 对于当前节点,计算该节点到目标节点的估算成本(一般使用曼哈顿距离或欧几里得距离),并将其加入已探索列表。

  3. 探索当前节点相邻的节点,对于每个相邻节点,计算从起点到该节点的实际成本(一般指每个节点的移动代价,也可以考虑更复杂的因素如地形、距离等),将该节点加入开放列表。

  4. 对于开放列表中的节点,按照实际成本和估算成本的总和排序,选择代价最小的节点作为下一个探索的节点,并将其从开放列表中移除。

  5. 重复步骤3和4,直到目标节点被加入已探索列表或开放列表为空。

  6. 如果目标节点被加入已探索列表中,则路径已经找到,从目标节点出发回溯到起点即可;否则,路径不存在。

A寻路算法通过估算代价,减少了搜索的空间,大大提高了搜索效率,同时保证了找到的路径是最短的。因此在实际应用中,A寻路算法广泛应用于游戏、机器人等领域。

以下是一个简单的Lua实现A*寻路算法的示例代码。假设我们有一个5x5的网格,其中0表示可通过的路径,1表示障碍物。我们要从(1,1)到(5,5)找到最短路径:

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
-- 表示网格的二维数组
local grid = {
{0,0,0,0,0},
{0,1,0,1,0},
{0,1,0,0,0},
{0,0,1,1,0},
{0,0,0,0,0}
}
local start = {x = 1, y = 1} -- 起点
local goal = {x = 5, y = 5} -- 终点

-- 表示节点的结构体
local Node = {}
function Node:new(x, y, parent)
local obj = {
x = x,
y = y,
parent = parent,
g = 0,
h = 0,
f = 0
}
self.__index = self
setmetatable(obj, self)
return obj
end

-- 计算两个节点之间的曼哈顿距离
function Node:manhattan_distance(other)
return math.abs(self.x - other.x) + math.abs(self.y - other.y)
end

-- 判断一个节点是否在网格内且可通过
function is_valid_node(x, y)
if x < 1 or x > #grid or y < 1 or y > #grid[1] then
return false
end
if grid[x][y] == 1 then
return false
end
return true
end

-- 查找路径
function find_path(start, goal)
local open_set = {start}
local closed_set = {}

while #open_set > 0 do
-- 选取F值最小的节点
local curr = open_set[1]
for i = 2, #open_set do
if open_set[i].f < curr.f then
curr = open_set[i]
end
end

-- 如果已经到达终点,则返回路径
if curr.x == goal.x and curr.y == goal.y then
local path = {}
while curr do
table.insert(path, 1, curr)
curr = curr.parent
end
return path
end

-- 将当前节点从开放列表中删除,加入已探索列表
for i, node in ipairs(open_set) do
if node == curr then
table.remove(open_set, i)
break
end
end
table.insert(closed_set, curr)

-- 对当前节点的相邻节点进行探索
for dx = -1, 1 do
for dy = -1, 1 do
if dx ~= 0 or dy ~= 0 then
local x = curr.x + dx
local y = curr.y + dy
if is_valid_node(x, y) then
local neighbor = Node:new(x, y, curr)
neighbor.g = curr.g + 1
neighbor.h = neighbor:manhattan_distance(goal)
neighbor.f = neighbor.g + neighbor.h

-- 如果相邻节点已经在已探索列表中,则忽略
local in_closed_set = false
for i, node in ipairs(closed_set) do
if node.x == neighbor.x and node.y == neighbor.y then
in_closed_set = true
break
end
end
if in_closed_set then
goto continue
end

-- 如果相邻节点已经在开放列表中,则更新其代价
local in_open_set = false
for i, node in ipairs(open_set) do
if node.x == neighbor.x and node.y == neighbor.y then
in_open_set = true
if neighbor.g < node.g then
node.g = neighbor.g
node.parent = neighbor.parent
end
break
end
end

-- 否则将相邻节点加入开放列表
if not in_closed_set and not in_open_set then
table.insert(open_set, neighbor)
end

::continue::
end
end
end
end
end

-- 无法到达终点,返回nil
return nil
end

-- 测试
local path = find_path(start, goal)
if path then
for i, node in ipairs(path) do
print(node.x, node.y)
end
else
print("Path not found")
end

在这个示例中,我们首先定义了一个网格,起点和终点。然后定义了一个Node结构体,表示节点信息,包括坐标(x,y)、父节点(parent)、实际成本(g)、估算成本(h)和总代价(f)。

接下来实现了find_path函数,用于在网格中查找路径。该函数首先定义了两个列表,分别为开放列表(open_set)和已探索列表(closed_set)。然后将起点加入开放列表。

在每次循环中,从开放列表中选取F值最小的节点进行探索,查找其相邻节点。对于每个相邻节点,如果其在已探索列表中则忽略,否则计算其代价,并将其加入开放列表中。如果找到终点,则从终点开始往回回溯父节点,得到完整的路径。

需要注意的是,由于Lua中goto语句的语义和其他编程语言有所不同,这里使用了一个continue标签实现了类似其他语言的continue功能。