此问题涉及A*
算法及其三种变体:
为了解决我的问题,我将使用python(v3 +),因为它有助于提高可读性。有许多图形数据结构(AM,AL,DOK,CRS等)。由于A*
仅对顶点集执行操作(假设顶点集具有前驱和后继的本地知识),我将使用邻接列表...或更具体地说是邻接字典(散列),例如:
vertex_set = {
"a": {"b", "c"},
"b": {"c"},
...
}
为了提供一个启动点,下面提供了一个简单A*
的实现:
from math import inf
def a_star_distance_between(start, goal):
# cost_estimate may go here
# but I set it to a constant zero transforming A* to a greedy breadth first
# search as this estimate will vary by your use case
cost_estimate = 0
return cost_estimate
def reconstruct_path(came_from, current):
total_path = [current]
while current in came_from.keys():
current = came_from[current]
total_path.append(current)
total_path.reverse()
return total_path
def a_star_heuristic_between(a, b):
# your heuristic goes here
# dummy heuristic just for functionality
heuristic = len(vertex_set[a]) + len(vertex_set[b])
return 1 / heuristic
def a_star_lowest_f_score(f_scores, nodes_currently_discovered):
f_min_val = inf
f_min_key = ""
for node in nodes_currently_discovered:
val = f_scores[node]
if val < f_min_val:
f_min_key = node
f_min_val = val
return f_min_key
def a_star(start, stop):
nodes_already_evaluated = set() # a.k.a. closed set
nodes_currently_discovered = {start} # a.k.a. open set
came_from = dict()
# for each node, cost of getting from start node to that node
g_score = {v: inf for v in list(vertex_set.keys())}
g_score[start] = 0
# for each node, cost of getting from the start node to the goal by passing through that node
f_score = {v: inf for v in list(vertex_set.keys())}
f_score[start] = 1 # normally a_star_heuristic_between(start, stop), 1 here because of hard coded value above
while nodes_currently_discovered:
current = a_star_lowest_f_score(f_score, nodes_currently_discovered)
if current == stop:
return reconstruct_path(came_from, current)
nodes_currently_discovered.remove(current)
nodes_already_evaluated.add(current)
for neighbor in vertex_set[current]:
if neighbor in nodes_already_evaluated:
continue
if neighbor not in nodes_currently_discovered:
nodes_currently_discovered.add(neighbor)
tentative_g_score = g_score[current] + a_star_distance_between(current, neighbor)
if tentative_g_score >= g_score[neighbor]:
continue # not a better path
# best path until now
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
f_score[neighbor] = g_score[neighbor] + a_star_heuristic_between(neighbor, stop)
在上面提到的三个变体中,从上面的实现中最容易做到的是(2)多目标:
from statistics import mean
def multi_target_a_star(start, stops):
# CHANGE
stops_paths = {stop: None for stop in stops}
nodes_already_evaluated = set() # a.k.a. closed set
nodes_currently_discovered = {start} # a.k.a. open set
came_from = dict()
# for each node, cost of getting from start node to that node
g_score = {v: inf for v in list(vertex_set.keys())}
g_score[start] = 0
# for each node, cost of getting from the start node to the goal by passing through that node
f_score = {v: inf for v in list(vertex_set.keys())}
f_score[start] = 1 # normally a_star_heuristic_between(start, stop), 1 here because of hard coded value
while nodes_currently_discovered:
current = a_star_lowest_f_score(f_score, nodes_currently_discovered)
if current == stop:
# CHANGE
stop_paths[current] = reconstruct_path(came_from, current)
if all([v != None for k, v in stops_paths.items()]):
return stops_paths
nodes_currently_discovered.remove(current)
nodes_already_evaluated.add(current)
for neighbor in vertex_set[current]:
if neighbor in nodes_already_evaluated:
continue
if neighbor not in nodes_currently_discovered:
nodes_currently_discovered.add(neighbor)
tentative_g_score = g_score[current] + a_star_distance_between(current, neighbor)
if tentative_g_score >= g_score[neighbor]:
continue # not a better path
# best path until now
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
# CHANGE
f_score[neighbor] = g_score[neighbor] + mean([a_star_heuristic_between(neighbor, stop) for stop in stops])
def a_star(start, stop, max_depth=inf):
nodes_already_evaluated = set() # a.k.a. closed set
nodes_currently_discovered = {start} # a.k.a. open set
came_from = dict()
# for each node, cost of getting from start node to that node
g_score = {v: inf for v in list(vertex_set.keys())}
g_score[start] = 0
# for each node, cost of getting from the start node to the goal by passing through that node
f_score = {v: inf for v in list(vertex_set.keys())}
f_score[start] = 1 # normally a_star_heuristic_between(start, stop), 1 here because of hard coded value above
# keep track of distance. This is not the most efficient way to do so. However it lets us not have to modify our distance and heuristic functions.
d_score = {v: inf for v in list(vertex_set.keys())}
d_score[start] = 0
while nodes_currently_discovered:
current = a_star_lowest_f_score(f_score, nodes_currently_discovered)
if current == stop:
return reconstruct_path(came_from, current)
nodes_currently_discovered.remove(current)
nodes_already_evaluated.add(current)
# CHANGE: test for depth
if d_score[current] + 1 > max_depth:
# NOTE: at this point current will NOT be re-evaluated again even if there is a path where getting to current is less than max_depth
# this stems from current being placed in nodes_already_evaluated as well as that the node and distance are not kept together in a tuple, e.g. (node, dist) - which requires updating a couple of functions.
continue
for neighbor in vertex_set[current]:
if neighbor in nodes_already_evaluated:
continue
if neighbor not in nodes_currently_discovered:
nodes_currently_discovered.add(neighbor)
# CHANGE
d_score[neighbor] = d_score[current] + 1
tentative_g_score = g_score[current] + a_star_distance_between(current, neighbor)
if tentative_g_score >= g_score[neighbor]:
continue # not a better path
# best path until now
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
f_score[neighbor] = g_score[neighbor] + a_star_heuristic_between(neighbor, stop)
我的问题是如何实现A*
的版本**(见注释),可以按如下方式调用:
a_star(start, stops, max_depth, num_paths, max_duration)
**注意:根据所做的更改,结果路径搜索可能不再与A*
相比,因为它可能会失去完整性。我使用&#34;版本&#34;松散地简单地表示在这种情况下它基于A*
。
其中:
start
:起始点stops
:从头开始查找路径的顶点列表(例如start -> stops[0]
,start -> stops[1]
,...,start -> stops[-1]
)max_depth
:一个整数,表示应该探索的最大路径长度,例如max_depth=3
开始 - (1) - &gt; a - (2) - &gt; b - (3) - &gt;停止num_paths
:每次开始和停止之间要查找的最大路径数,例如如果len(stops)=2
那么最多应该有6条路径max_duration
:函数终止前允许的最长运行时间并返回到目前为止已找到的内容我知道这些变体(不包括最大深度)中的每一个都是与路径查找相关的主要主题。多路径受到各种基础前提的关注。有些方法尝试发送多个代理,例如Bee-Sensor。一种更简单的方法只是从图中删除第一个找到的路径中的那些顶点,然后在子图上重新运行算法(我觉得非常不满意)。如果a --> b --> c --> e
和a --> b --> d --> e
是路径,我希望两者都找到,而不是从评估中丢弃顶点b
和c
。
最近最有趣的贡献之一来自Yin and Yang, 2015。然而,类似于前面提到的删除找到路径的顶点的天真方法,此方法找到具有更大方差的路径(对于它们的用例而言非常好,不适用于我的)。
我已经实现了我对DFS和BFS的既定目标。然而,a-star的框架让我很难看到如何获得第二,第三等最佳路径。
对于多路径,我最初认为存储一个简单的路径列表就足够了,并检查它的长度是否达到了所需的量。但是,这不起作用,因为访问的节点被跟踪的方式分为两组:&#39;关闭&#39; (nodes_already_evaluated
)和&#39;打开&#39; (nodes_currently_discovered
)设置。如果路径A*
返回a --> b --> c
,则此时封闭集至少为{a, b, c}
,我们不希望将b
从评估中删除为a -- > b -- d --> c
可能是第二个最好的路径。但是,在开放集中包含b
和目标(c
)只会导致两次相同的路径并重新计算已经确定的值。特别是如果这种多路径方法与多目标混合。
一个有效的问题可能是&#34;为什么你想要一个多路径,多目标&#34;算法,您可以并行启动多路径单目标搜索。在距离函数恒定或未知的情况下,A*
成为贪婪的广度优先搜索算法,因此无论最终目标如何,所探索的边界都是相同的。上面给出的多目标实现只是不断扩展边界,直到达到所有目标,这样更有效。
我非常感谢为查找路径查找功能提供的任何指导,其中包含先前列出的参数(start,stops,max_depth,num_paths,duration),这些参数至少包含启发式函数并具有竞争性运行时。
出于测试目的,我提供了以下小图:
vertex_set = {
1: {3, 4, 13, 21},
2: {3, 20},
3: {1, 2, 4, 5, 23},
4: {1, 3, 6},
5: {3, 7},
6: {4, 8, 16, 23},
7: {5, 9},
8: {6, 10, 11},
9: {7, 12},
10: {13, 14, 15},
11: {15, 16},
12: {9, 20},
13: {1, 10, 17},
14: {10, 17, 18},
15: {10, 11},
16: {6, 11, 18, 19, 20},
17: {13, 14},
18: {14, 16, 21},
19: {16, 22},
20: {2, 12, 16},
21: {1, 18},
22: {19},
23: {3, 6, 22}
}
@tobias_k指出了对上述代码的几种优化。例如使用优先级堆来查找最低f分数顶点。
此外,我想指出上述代码不适用于任何严肃的生产目的。相反,如最初所述,以更易读的语言实现,以帮助讨论如何修改A*
算法的核心概念。
还有其他优化措施。例如,对于深度锁定变体(搜索&lt; =固定长度的路径),它将节省内存和时间以不存储d_score
中的所有距离;相反,nodes_already_evaluated
和nodes_currently_discovered
(分别为封闭和开放集合)可以存储(vertex, distance_when_discovered)
的元组。这将允许不同深度的两个不同顶点可到达的顶点不止一次地添加到nodes_currently_discovered
(开放集) - 这取决于用例,这是假定的期望特征。
沿着这些方向,@tobias_k建议将顶点的f_score
存储在优先级堆的元组中。
我非常欣赏有关优化的见解。但是,这不是问题的主要目的。