环境:win10 语言: Python3.6 编译器:pycharm
先看效果图(红色:终点 黄色:起点 黑色:障碍 绿色路径)
一、A*算法:
A*算法是一种启发式搜索算法,它不需遍历所有节点,只是利用包含问题启发式信息的评价函数对节点进行排序,使搜索方向朝着最有可能找到目标并产生最优解的方向。它的独特之处是检查最短路径中每个可能的节点时引入了全局信息,对当前节点距终点的距离做出估计,并作为评价节点处于最短路径上的可能性度量。
A*算法中引入了评估函数,评估函数为:f(n)=g(n)+h(n) 其中:n是搜索中遇到的任意状态。g(n)是沿路径从起点到n点的移动耗费。h(n)是对n到目标状态代价的启发式估计。即评估函数f ( n) 是从初始节点到达节点n 处已经付出的代价与节点n 到达目标节点的接近程度估价值的总和。
这里我们定义n点到目标点的最小实际距离为h(n)*,A*算法要满足的条件为:h(n)<=h(n)* 。迷宫走的时候只能往上下左右走,每走一步,代价为1,H值可以用不同的方法估算。我们这里使用的方法被称为曼哈顿方法,它计算从当前格到目的格之间水平和垂直的方格的数量总和,即: h(n)=|endPoint.x – n.x|+ |endpoint.y – n.y|这里endPoint表示迷宫的目标点,n表示当前点,很明显这里h(n)<=h(n)*。
二、类的定义
1、mapPoint :代表图中的每一个格子,搜索路径的每一个格子,记录的信息有:
self.x = x self.y = y 当前节点的坐标位置
self.distanceStart = distanceStart 记录下由起点走到该节点的耗散值(由起点走到该节点的路径的距离,该节点的下一个节点会加1)
self.endX = endX self.endY = endY 目标节点
self.parentPoint = parentPoint # 前一个节点
方法:
评价函数:由于该评价函数比较容易计算,我直接放到该类中
def evaluate(self): return self.distanceStart + abs(self.x - self.endX) + abs(self.y - self.endY)
判断两个节点是否是同一个位置的
def isEq(self, point): if point.x == self.x and point.y == self.y: return True else: return False
以下是A*算法
def Astart(self):
# 将起点放到open表中
self.openList.append(self.startPoint)
while (not self.isOK):
# 先检查终点是否在open表中 ,没有继续,有则结束
if self.inOpenList(self.endPoint) != -1: # 在open表中
self.isOK = True #
self.end = self.openList[self.inOpenList(self.endPoint)]
self.route.append(self.end)
self.te = self.end
while (self.te.parentPoint != 0):
self.te = self.te.parentPoint
self.route.append(self.te)
else:
self.sortOpenList() # 将估值最小的节点放在index = 0
current_min = self.openList[0] # 估值最小节点
self.openList.pop(0)
self.closeList.append(current_min)
# 开拓current_min节点,并放到open 表
if current_min.x - 1 >= 0: # 没有越界
if (self.mapStatus[current_min.y][current_min.x - 1]) != 0: # 非障碍,可开拓
self.temp1 = mapPoint(current_min.x - 1, current_min.y, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp1) != -1: # open表存在相同的节点
if self.temp1.evaluate() < self.openList[self.inOpenList(self.temp1)].evaluate():
self.openList[self.inOpenList(self.temp1)] = self.temp1
elif self.inCloseList(self.temp1) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp1.evaluate() < self.closeList[self.inCloseList(self.temp1)].evaluate():
self.closeList[self.inCloseList(self.temp1)] = self.temp1
else: # open 、 close表都不存在 temp1
self.openList.append(self.temp1)
if current_min.x + 1 < 15:
if (self.mapStatus[current_min.y][current_min.x + 1]) != 0: # 非障碍,可开拓
self.temp2 = mapPoint(current_min.x + 1, current_min.y, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp2) != -1: # open表存在相同的节点
if self.temp2.evaluate() < self.openList[self.inOpenList(self.temp2)].evaluate():
self.openList[self.inOpenList(self.temp2)] = self.temp2
elif self.inCloseList(self.temp2) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp2.evaluate() < self.closeList[self.inCloseList(self.temp2)].evaluate():
self.closeList[self.inCloseList(self.temp2)] = self.temp2
else:
self.openList.append(self.temp2)
if current_min.y - 1 >= 0:
if (self.mapStatus[current_min.y - 1][current_min.x]) != 0: # 非障碍,可开拓
self.temp3 = mapPoint(current_min.x, current_min.y - 1, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp3) != -1: # open表存在相同的节点
if self.temp3.evaluate() < self.openList[self.inOpenList(self.temp3)].evaluate():
self.openList[self.inOpenList(self.temp3)] = self.temp3
elif self.inCloseList(self.temp3) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp3.evaluate() < self.closeList[self.inCloseList(self.temp3)].evaluate():
self.closeList[self.inCloseList(self.temp3)] = self.temp3
else:
self.openList.append(self.temp3)
if current_min.y + 1 < 15:
if (self.mapStatus[current_min.y + 1][current_min.x]) != 0: # 非障碍,可开拓
self.temp4 = mapPoint(current_min.x, current_min.y + 1, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp4) != -1: # open表存在相同的节点
if self.temp4.evaluate() < self.openList[self.inOpenList(self.temp4)].evaluate():
self.openList[self.inOpenList(self.temp4)] = self.temp4
elif self.inCloseList(self.temp4) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp4.evaluate() < self.closeList[self.inCloseList(self.temp4)].evaluate():
self.closeList[self.inCloseList(self.temp4)] = self.temp4
else:
self.openList.append(self.temp4)
def drawMapBlock(self, event):
x, y = event.x, event.y
if (30 <= x <= 480) and (30 <= y <= 480):
i = int((x // 30) - 1)
j = int((y // 30) - 1)
# 记录下起止点,并不能选择多个起点或者多个终点
if self.blockcolorIndex == 1 and not self.selectedStart:
self.startPoint = mapPoint(i, j, 0, 0, 0, 0)
self.selectedStart = True
self.canvas.create_rectangle((i + 1) * 30, (j + 1) * 30, (i + 2) * 30, (j + 2) * 30,
fill=self.blockcolor[self.blockcolorIndex])
self.blockcolorIndex = 0
elif self.blockcolorIndex == 2 and not self.selectedEnd:
self.endPoint = mapPoint(i, j, 0, 0, 0, 0)
self.selectedEnd = True
self.canvas.create_rectangle((i + 1) * 30, (j + 1) * 30, (i + 2) * 30, (j + 2) * 30,
fill=self.blockcolor[self.blockcolorIndex])
self.blockcolorIndex = 0
else:
self.canvas.create_rectangle((i + 1) * 30, (j + 1) * 30, (i + 2) * 30, (j + 2) * 30,
fill=self.blockcolor[self.blockcolorIndex])
self.mapStatus[j][i] = self.blockcolorIndex
# 检查终点是否在open表中
def endInOpenList(self):
for i in self.openList:
if self.endPoint[0] == i.x and self.endPoint[1] == i.y:
return True
return False
# 将节点加进open表前,检查该节点是否在open表中
def inOpenList(self, p1):
for i in range(0, len(self.openList)):
if p1.isEq(self.openList[i]):
return i
return -1
# 将节点加进open表前,检查该节点是否在close表中
# 若在返回索引,不在返回-1
def inCloseList(self, p1):
for i in range(0, len(self.closeList)):
if p1.isEq(self.closeList[i]):
return i
return -1
# 将 估值最小的 排在 index = 0
def sortOpenList(self):
if len(self.openList) > 0:
if len(self.openList) > 1:
for i in range(1, len(self.openList)):
if self.openList[i].evaluate() < self.openList[0].evaluate():
self.t = self.openList[0]
self.openList[0] = self.openList[i]
self.openList[i] = self.t
定义的数据结构有:
self.openList = [] # open表 self.closeList = [] # close表 self.route = [] # 路径列表
A*算法的主要过程:
1、先将起点放进open表中
2、检查终点是否在open表中,否的话,从open表中取出评价值最低的节点作为当前节点,并向四周探索,将上、下、左、右 里面非障碍的子节点,并检查子节点是否已经存在于open表或close表,若是存在open表中,则比较两个子节点与open表中的节点的评价值,若是open表中的比较大,则将open表中的替换,否则不操作;若是close表中存在且close表的节点的评价值比较大,则放进该子节点open表,否则不做操作。 然后将当前节点移出open表,并放进close表。
3、循环第2点,知道检测到终点在open 表中,然后将终点记录在路径列表中route = [] 中,并通过循环不断得到前一个节点,一直记录下来,直达没有前一个节点为止。
所有代码:
# -*- coding: utf-8 -*-
from tkinter import Button
from tkinter import Tk
from tkinter import Canvas
import numpy as np
class Maze(object):
def __init__(self):
self.blockcolorIndex = 0
self.blockcolor = ['black', 'yellow', 'red', 'green'] # 障碍颜色为黑色、起点黄色 终点红色、路径绿色
self.mapStatus = np.ones((15, 15), dtype=int) # 地图状态数组(全0数组) 1无障碍 0障碍
self.startPoint = 'start' # 起点
self.endPoint = 'end' # 终点
self.selectedStart = False # 是否选了起点 默认否
self.selectedEnd = False # 是否选了终点 默认否
self.openList = [] # open表
self.closeList = [] # close表
self.isOK = False # 是否已经结束
self.route = [] # 路径列表
# UI
self.root = Tk()
self.root.title('走迷宫(A*算法)')
self.root.geometry("800x800+300+0")
self.btn_obstacle = Button(self.root, text="选择障碍", command=self.selectobstacle)
self.btn_obstacle.pack()
self.btn_start = Button(self.root, text="选择起点", command=self.selectstart)
self.btn_start.pack()
self.btn_end = Button(self.root, text="选择终点", command=self.selectend)
self.btn_end.pack()
self.btn_action = Button(self.root, text="开始寻路", command=self.selectaction)
self.btn_action.pack()
self.btn_restart = Button(self.root, text="重新开始", command=self.selectrestart)
self.btn_restart.pack()
self.canvas = Canvas(self.root, width=500, height=500, bg="white")
self.canvas.pack()
for i in range(1, 17):
self.canvas.create_line(30, 30 * i, 480, 30 * i) # 横线
self.canvas.create_line(30 * i, 30, 30 * i, 480) # 竖线
self.canvas.bind("", self.drawMapBlock)
self.root.mainloop()
def selectrestart(self):
self.mapStatus = np.ones((15, 15), dtype=int) # 地图状态数组(全0数组) 1无障碍 0障碍
self.startPoint = 'start'
self.endPoint = 'end'
self.selectedStart = False # 是否选了起点 默认否
self.selectedEnd = False # 是否选了终点 默认否
self.openList = [] # open表
self.closeList = [] # close表
self.isOK = False # 是否已经结束
self.route = []
self.canvas.destroy()
self.canvas = Canvas(self.root, width=500, height=500, bg="white")
self.canvas.pack()
for i in range(1, 17):
self.canvas.create_line(30, 30 * i, 480, 30 * i) # 横线
self.canvas.create_line(30 * i, 30, 30 * i, 480) # 竖线
self.canvas.bind("", self.drawMapBlock)
def selectobstacle(self):
self.blockcolorIndex = 0 # 'black'
def selectstart(self):
if not self.selectedStart:
self.blockcolorIndex = 1 # yellow
else:
self.blockcolorIndex = 0 # black
def selectend(self):
if not self.selectedEnd:
self.blockcolorIndex = 2 # red
else:
self.blockcolorIndex = 0 # black
def selectaction(self):
self.blockcolorIndex = 3 # 'green'
self.Astart()
self.route.pop(-1)
self.route.pop(0)
for i in self.route:
self.canvas.create_rectangle((i.x + 1) * 30, (i.y + 1) * 30, (i.x + 2) * 30, (i.y + 2) * 30, fill='green')
def Astart(self):
# 将起点放到open表中
self.openList.append(self.startPoint)
while (not self.isOK):
# 先检查终点是否在open表中 ,没有继续,有则结束
if self.inOpenList(self.endPoint) != -1: # 在open表中
self.isOK = True #
self.end = self.openList[self.inOpenList(self.endPoint)]
self.route.append(self.end)
self.te = self.end
while (self.te.parentPoint != 0):
self.te = self.te.parentPoint
self.route.append(self.te)
else:
self.sortOpenList() # 将估值最小的节点放在index = 0
current_min = self.openList[0] # 估值最小节点
self.openList.pop(0)
self.closeList.append(current_min)
# 开拓current_min节点,并放到open 表
if current_min.x - 1 >= 0: # 没有越界
if (self.mapStatus[current_min.y][current_min.x - 1]) != 0: # 非障碍,可开拓
self.temp1 = mapPoint(current_min.x - 1, current_min.y, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp1) != -1: # open表存在相同的节点
if self.temp1.evaluate() < self.openList[self.inOpenList(self.temp1)].evaluate():
self.openList[self.inOpenList(self.temp1)] = self.temp1
elif self.inCloseList(self.temp1) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp1.evaluate() < self.closeList[self.inCloseList(self.temp1)].evaluate():
self.closeList[self.inCloseList(self.temp1)] = self.temp1
else: # open 、 close表都不存在 temp1
self.openList.append(self.temp1)
if current_min.x + 1 < 15:
if (self.mapStatus[current_min.y][current_min.x + 1]) != 0: # 非障碍,可开拓
self.temp2 = mapPoint(current_min.x + 1, current_min.y, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp2) != -1: # open表存在相同的节点
if self.temp2.evaluate() < self.openList[self.inOpenList(self.temp2)].evaluate():
self.openList[self.inOpenList(self.temp2)] = self.temp2
elif self.inCloseList(self.temp2) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp2.evaluate() < self.closeList[self.inCloseList(self.temp2)].evaluate():
self.closeList[self.inCloseList(self.temp2)] = self.temp2
else:
self.openList.append(self.temp2)
if current_min.y - 1 >= 0:
if (self.mapStatus[current_min.y - 1][current_min.x]) != 0: # 非障碍,可开拓
self.temp3 = mapPoint(current_min.x, current_min.y - 1, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp3) != -1: # open表存在相同的节点
if self.temp3.evaluate() < self.openList[self.inOpenList(self.temp3)].evaluate():
self.openList[self.inOpenList(self.temp3)] = self.temp3
elif self.inCloseList(self.temp3) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp3.evaluate() < self.closeList[self.inCloseList(self.temp3)].evaluate():
self.closeList[self.inCloseList(self.temp3)] = self.temp3
else:
self.openList.append(self.temp3)
if current_min.y + 1 < 15:
if (self.mapStatus[current_min.y + 1][current_min.x]) != 0: # 非障碍,可开拓
self.temp4 = mapPoint(current_min.x, current_min.y + 1, current_min.distanceStart + 1,
self.endPoint.x, self.endPoint.y, current_min)
if self.inOpenList(self.temp4) != -1: # open表存在相同的节点
if self.temp4.evaluate() < self.openList[self.inOpenList(self.temp4)].evaluate():
self.openList[self.inOpenList(self.temp4)] = self.temp4
elif self.inCloseList(self.temp4) != -1: # 否则查看close表是否存在相同的节点(存在)
if self.temp4.evaluate() < self.closeList[self.inCloseList(self.temp4)].evaluate():
self.closeList[self.inCloseList(self.temp4)] = self.temp4
else:
self.openList.append(self.temp4)
def drawMapBlock(self, event):
x, y = event.x, event.y
if (30 <= x <= 480) and (30 <= y <= 480):
i = int((x // 30) - 1)
j = int((y // 30) - 1)
# 记录下起止点,并不能选择多个起点或者多个终点
if self.blockcolorIndex == 1 and not self.selectedStart:
self.startPoint = mapPoint(i, j, 0, 0, 0, 0)
self.selectedStart = True
self.canvas.create_rectangle((i + 1) * 30, (j + 1) * 30, (i + 2) * 30, (j + 2) * 30,
fill=self.blockcolor[self.blockcolorIndex])
self.blockcolorIndex = 0
elif self.blockcolorIndex == 2 and not self.selectedEnd:
self.endPoint = mapPoint(i, j, 0, 0, 0, 0)
self.selectedEnd = True
self.canvas.create_rectangle((i + 1) * 30, (j + 1) * 30, (i + 2) * 30, (j + 2) * 30,
fill=self.blockcolor[self.blockcolorIndex])
self.blockcolorIndex = 0
else:
self.canvas.create_rectangle((i + 1) * 30, (j + 1) * 30, (i + 2) * 30, (j + 2) * 30,
fill=self.blockcolor[self.blockcolorIndex])
self.mapStatus[j][i] = self.blockcolorIndex
# 检查终点是否在open表中
def endInOpenList(self):
for i in self.openList:
if self.endPoint[0] == i.x and self.endPoint[1] == i.y:
return True
return False
# 将节点加进open表前,检查该节点是否在open表中
def inOpenList(self, p1):
for i in range(0, len(self.openList)):
if p1.isEq(self.openList[i]):
return i
return -1
# 将节点加进open表前,检查该节点是否在close表中
# 若在返回索引,不在返回-1
def inCloseList(self, p1):
for i in range(0, len(self.closeList)):
if p1.isEq(self.closeList[i]):
return i
return -1
# 将 估值最小的 排在 index = 0
def sortOpenList(self):
if len(self.openList) > 0:
if len(self.openList) > 1:
for i in range(1, len(self.openList)):
if self.openList[i].evaluate() < self.openList[0].evaluate():
self.t = self.openList[0]
self.openList[0] = self.openList[i]
self.openList[i] = self.t
class mapPoint(object):
def __init__(self, x, y, distanceStart, endX, endY, parentPoint):
self.x = x
self.y = y
self.distanceStart = distanceStart
self.endX = endX
self.endY = endY
self.parentPoint = parentPoint # 前一个节点
def evaluate(self):
return self.distanceStart + abs(self.x - self.endX) + abs(self.y - self.endY)
def isEq(self, point):
if point.x == self.x and point.y == self.y:
return True
else:
return False
def main():
Maze()
if __name__ == '__main__':
main()