def A_star(pos_start, pos_end, map):
"""
:param pos_start: 起点位置, 例如(3,3)
:param pos_end: 终点位置, 例如(9,9)
:param map: 用于记录地图信息的二维矩阵,0表示空白,1表示墙体等障碍物,9表示食物(终点)
:return:
"""
map_width = len(map[0]) #地图的宽
map_height = len(map) #地图的高
open_list = order_seq() #order_seq是我自己写的一个类,它会自动维护一个升序队列
close_list = [] #close_list
open_list.append((0, 0, pos_start)) #首先将起点加入openlist中
count = 0 #记录遍历次数
while True:
count += 1 #遍历次数+1
if open_list.is_empty(): #如果open表为空,则说明没有路径可达终点,算法结束
return False, {}, 0
else: #如果open表不为空,则取出队列头,注意这里的pop是被我在order_seq类重写过的,它在这里取的是队列头
temp_tuple = open_list.pop() # temp_tuple like (1,2,(1,2)) referring to (F,G,(this_position)
pos = temp_tuple[2] #pos为从open表中取出的坐标,例如(1,1)
if pos == pos_end: #如果取出来的这个点就是终点,则算法结束,返回
return True, open_list.last_dict,count
#得到取出点的F值、G值以及坐标
F = temp_tuple[0]
G = temp_tuple[1]
x = temp_tuple[2][0]
y = temp_tuple[2][1]
#接下来按照左、上、右、下的顺序将可走的点并且未在close表中的点加入到open表中,并计算它们的F值和G值
#以左边的点为例
if y - 1 >= 0 and map[x][y - 1] in [0, 9] and (x, y - 1) not in close_list: # left
New_G = G + 1 #新的G值等于当前点的G值加1,即经过的步数
New_H = abs(pos_end[0] - x) + abs(pos_end[1] - (y - 1)) #这个点到终点的曼哈顿距离 H=|x1-x2|+|y1-y2|
New_F = New_G + New_H #F=G+H
New_tuple = (New_F, New_G, (x, y - 1))
open_list.append(New_tuple, pos) #将新的点加入openlist中
if x - 1 >= 0 and map[x - 1][y] in [0, 9] and (x - 1, y) not in close_list: # up
New_G = G + 1
New_H = abs(pos_end[0] - (x - 1)) + abs(pos_end[1] - y)
New_F = New_G + New_H
New_tuple = (New_F, New_G, (x - 1, y))
open_list.append(New_tuple, pos)
if y + 1 < map_width and map[x][y + 1] in [0, 9] and (x, y + 1) not in close_list: # right
New_G = G + 1
New_H = abs(pos_end[0] - x) + abs(pos_end[1] - (y + 1))
New_F = New_G + New_H
New_tuple = (New_F, New_G, (x, y + 1))
open_list.append(New_tuple, pos)
if x + 1 < map_height and map[x + 1][y] in [0, 9] and (x + 1, y) not in close_list: # down
New_G = G + 1
New_H = abs(pos_end[0] - (x + 1)) + abs(pos_end[1] - y)
New_F = New_G + New_H
New_tuple = (New_F, New_G, (x + 1, y))
open_list.append(New_tuple, pos)
close_list.append(pos)
在A*算法中,需要维护一个open表,每次取出open表中F值最小的点,为了简化操作,我自己写了一个类order_seq
class order_seq(): # ascending
def __init__(self):
self.data = []
self.last_dict = {} # create mapping from this pos to last pos eg: (1,2) -> (3,4) means the last position of (1,2) is (3,4)
def append(self, tuple, last_pos=(-1, -1)): # tuple like ( 1,1,(1,2)) refering to (F,G,(this_position))
insert_tag = True
if self.data == []:
self.data.append(tuple)
self.last_dict[tuple[2]] = last_pos
else:
for item in self.data:
if tuple[2] == item[2]:
if tuple[1] < item[1]:
self.data.remove(item)
else:
insert_tag = False
break
if insert_tag:
is_insert = False
for id, item in enumerate(self.data):
if tuple[0] <= item[0]:
self.data.insert(id, tuple)
self.last_dict[tuple[2]] = last_pos
is_insert = True
break
if not is_insert:
self.data.append(tuple)
self.last_dict[tuple[2]] = last_pos
def pop(self):
return self.data.pop(0)
def is_empty(self):
if self.data == []:
return True
else:
return False
最后,这里的A*算法返回的是什么东西呢?我让其返回了三样东西:①是否找到路径?(True or False)②一个用于记录某位置的上一个位置的字典(映射)last_dict ③while循环遍历的次数(这个可以根据需要自行删除)
这里详细介绍一下last_dict是干嘛用的:
它里面存放的形式如下:
该位置 | 上一个位置 |
---|---|
(1,2) | (1,1) |
(1,1) | (1,0) |
(1,0) | (0,0) |
(0,0) | (-1,-1) |
last_dict建立的就是该位置到上一个位置的映射。
如上表的含义为(1,2)的上一步是(1,1), (1,1)的上一步是(1,0),(1,0)的上一步是(0,0),由于(0,0)的上一步是(-1,-1)说明(0,0)是起点(在代码中首先将起点加入open表中时,会首先建立一个(起点)->(-1,-1)的映射)。通过这样的方法可以还原出找到的路径。例如,假设上表中终点是(1,2),就可以逆向形成一个搜索链(1,2)->(1,1)->(1,0)->(0,0),这就是倒过来的路径。
这里提供还原逆向路径的函数:path_parser
def path_parser(food_pos,last_dict):
"""
food_pos:终点坐标,例如(9,9)
last_dict: 就是A*算法返回的last_dict
"""
path_list=[]
path_list.append(food_pos)
temp=path_dict[food_pos]
while temp != (-1,-1):
path_list.append(temp)
temp=path_dict[temp]
return path_list
最后就是一个简单的应用示例:
map = [[0, 0, 0, 1, 0],
[0, 0, 0, 1, 0],
[0, 0, 9, 1, 0],
[1, 1, 1, 1, 0],
[1, 0, 0, 0, 0]]
flag, dict,count = A_star((0, 0), (2, 2), map)
#解析路径
path_list=path_parser((2,2),dict))