8.6.4 路径规划
接下来定义一些实现路径搜索和测试的函数,在地图中实现路径规划功能。在这个项目中,使用了如下所示的路径规划算法。
- A*搜索算法(A Star Search):通过在图中逐步扩展估计总代价最小的节点来寻找从起始节点到目标节点的路径。A*算法同时考虑了启发式搜索和Dijkstra算法的优点,具有较高的搜索效率和路径质量。
- 最佳优先搜索算法(Best First Search):类似于A*算法,但最佳优先搜索算法只考虑启发式函数的值,不考虑已经花费的代价。因此,最佳优先搜索算法不一定能够找到最优解,但它可能更快地找到一个可接受的解。
- 深度优先搜索算法(Depth First Search):从起始节点开始,尽可能深地探索图中的每个分支,直到找到目标节点或者遍历完整个图。深度优先搜索算法可能会陷入无限循环,因此需要进行适当的限制。
- 广度优先搜索算法(Breadth First Search):从起始节点开始,逐层地向外扩展搜索,直到找到目标节点或者遍历完整个图。广度优先搜索算法保证找到的解是最优解,但在搜索过程中需要存储大量的中间状态,可能会占用较大的内存空间。
(1)下面的代码定义了一个名为 Point 的类,用于表示二维空间中的点。该类具有属性 x 和 y 分别表示点的 x 和 y 坐标,以及用于路径规划算法的额外属性和方法。其中,equal 方法用于比较两个点的坐标是否相等,从而判断它们是否代表同一个位置。这个类的设计旨在提供一种方便的方式来管理和操作二维空间中的点,特别是在路径规划算法中的应用场景中。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
self.f = 0
self.g = 0
self.h = 0
self.index = 0
self.parent = None
def equal(self, other):
if distance(self.x, self.y, other.x, other.y, 0) == 0:
return True
(2)下面的代码定义了两个函数,具体说明如下所示。
- 函数distance(x1, y1, x2, y2, want):用于计算两个点之间的距离。参数 `x1` 和 `y1` 是第一个点的坐标,参数 `x2` 和 `y2` 是第二个点的坐标,参数 `want` 用于指定计算欧几里得距离还是曼哈顿距离。如果 `want` 为 0 或者省略,则计算欧几里得距离;如果 `want` 为 1,则计算曼哈顿距离。函数根据给定的坐标计算出两点之间的距离,并返回结果。
- 函数find_in_ovals(x, y):用于在给定坐标 `(x, y)` 下查找是否存在一个城市(或椭圆)。函数遍历 `gui.ovals` 列表中的所有城市,对比给定坐标和城市的坐标,以判断给定坐标是否落在某个城市之内。如果找到匹配的城市,则返回该城市在列表中的索引;如果未找到匹配的城市,则返回 -1。
def distance(x1, y1, x2, y2, want):
if gui.evaluation == 0 or want == 1: # euclidean distance
return sqrt((x1 - x2)**2 + (y1 - y2)**2)
else: # manhattan distance
return abs(x1 - x2) + abs(y1 - y2)
def find_in_ovals(x, y):
i = 0
while i < len(gui.ovals):
(x1, y1) = gui.ovals[i]["coords"]
if distance(x, y, x1, y1, 0) == 0:
return i
i += 1
return -1
(3)函数append_to_stack用于将一个新的点添加到堆栈中,并根据指定的算法索引计算并设置新点的启发式函数值,代价函数值和总代价函数值,然后将其添加到堆栈中,并更新堆栈的顶部索引。
def append_to_stack(x, y, end_point, parent_point, index, stack, top, visited, index2):
point = Point(x - 5, y - 5)
point.parent = parent_point
if index == 0 or index == 1: # a star and best first search
point.h = distance(x - 5, y - 5, end_point.x, end_point.y, 0)
if index == 0: # a star
point.g = point.parent.g + distance(point.x, point.y, point.parent.x, point.parent.y, 1)
point.f = point.g + point.h
stack.append(point)
top += 1
visited[index2] = 1
return top
(4)函数add_neighbours的主要目的是为给定的父节点找到相邻的节点,并将它们添加到堆栈中。它通过遍历所有的连接线来实现这一目的。对于每条连接线,它检查连接线两端的节点是否与父节点相邻。如果某个节点是父节点的相邻节点且尚未被访问过,则将该节点添加到堆栈中。在添加节点时,它会调用`append_to_stack`函数来计算节点的代价值,并将节点添加到堆栈中。最后,函数返回更新后的堆栈顶部索引。
def add_neighbours(parent_point, stack, top, visited, end_point, index):
# find neighbours
i = 0
while i < len(gui.lines):
x1, y1, x2, y2 = gui.lines[i]["coords"]
index1 = find_in_ovals(x1 - 5, y1 - 5)
index2 = find_in_ovals(x2 - 5, y2 - 5)
if distance(x1 - 5, y1 - 5, parent_point.x, parent_point.y, 0) == 0 and visited[index2] == 0:
# print("Eklenen nokta: ", x2 - 5, y2 - 5)
top = append_to_stack(x2, y2, end_point, parent_point, index, stack, top, visited, index2)
elif distance(x2 - 5, y2 - 5, parent_point.x, parent_point.y, 0) == 0 and visited[index1] == 0:
# print("Eklenen nokta: ", x1 - 5, y1 - 5)
top = append_to_stack(x1, y1, end_point, parent_point, index, stack, top, visited, index1)
i += 1
return top
(5)函数a_star_and_best_first_search实现了A*搜索算法和最佳优先搜索算法。它使用了一个堆栈来存储待探索的节点,并通过迭代地从堆栈中取出节点来搜索路径。在搜索过程中,它会调用`add_neighbours`函数来添加节点的相邻节点,并根据具体的搜索算法对堆栈中的节点进行排序。当找到目标节点时,函数会停止搜索,并调用`paint`函数来绘制路径。最后,函数返回搜索得到的路径的距离、步数、堆栈的最大大小、弹出的节点数以及搜索所用的时间。
def a_star_and_best_first_search(index, start_time):
stack = []
found = False
top = 0
(x, y) = gui.selected_cities[0]
(end_x, end_y) = gui.selected_cities[1]
point = Point(x, y)
end = Point(end_x, end_y)
start = point
# print("Start {}, {}. End {}, {}".format(start.x, start.y, end.x, end.y))
point.index = find_in_ovals(x, y)
stack.append(point) # source
visited = np.zeros(gui.city_count)
visited[point.index] = 1
popped = 0
max_element = 0
while len(stack) > 0 and not found:
point = stack.pop(top)
# print("Çekilen nokta: ", point.x, point.y)
top -= 1
if point.equal(end):
found = True
print("Path found")
else:
top = add_neighbours(point, stack, top, visited, end, index)
stack.sort(key=lambda point: point.f, reverse=True) # sort max to min by h(n)
if len(stack) > max_element:
max_element = len(stack)
# print("Stack boyutu:", len(stack))
popped += 1
if found:
total_distance, step_size = paint(point, start, max_element, popped)
return total_distance, step_size, max_element, popped, time.time() - start_time
else:
message = "Path is not exist."
gui.step_label.config(text=message)
return 0, 0, 0, 0, time.time() - start_time
(6)函数depth_first_search实现了深度优先搜索算法。它使用一个堆栈来存储待探索的节点,并通过迭代地从堆栈中取出节点来搜索路径。在搜索过程中,它会调用`add_neighbours`函数来添加节点的相邻节点,并在添加节点后继续搜索。当找到目标节点时,函数会停止搜索,并调用`paint`函数来绘制路径。最后,函数返回搜索得到的路径的距离、步数、堆栈的最大大小、弹出的节点数以及搜索所用的时间。
def depth_first_search(start_time):
stack = []
found = False
top = 0
(x, y) = gui.selected_cities[0]
(end_x, end_y) = gui.selected_cities[1]
point = Point(x, y)
end = Point(end_x, end_y)
start = point
# print("Start {}, {}. End {}, {}".format(start.x, start.y, end.x, end.y))
point.index = find_in_ovals(x, y)
stack.append(point) # source
visited = np.zeros(gui.city_count)
visited[point.index] = 1
popped = 0
max_element = 0
while len(stack) > 0 and not found:
point = stack.pop(top)
# print("Çekilen nokta: ", point.x, point.y)
top -= 1
if point.equal(end):
found = True
print("Path found")
else:
top = add_neighbours(point, stack, top, visited, end, 2)
if len(stack) > max_element:
max_element = len(stack)
# print("Stack boyutu:", len(stack))
popped += 1
if found:
total_distance, step_size = paint(point, start, max_element, popped)
return total_distance, step_size, max_element, popped, time.time() - start_time
else:
message = "Path is not exist."
gui.step_label.config(text=message)
return 0, 0, 0, 0, time.time() - start_time