双向迪杰斯特拉算法(Bi Directional Dijkstra Algorithm)是一种用于在加权图中查找两个顶点之间最短路径的算法,是Dijkstra算法的一个变种,基本思想是:从两个搜索方向同时开始搜索——从起点到终点方向和从终点到起点方向同时进行迪杰斯特拉算法搜索,如果存在路径,那么最终两个方向的搜索会在某点相遇并终止,而这条路径就是最短距离路径。在某些情况下,双向迪杰斯特拉算法可以减少搜索空间大小,从而提高算法效率。其中也有分治法的思想
与迪杰斯特拉算法相比,双向迪杰斯特拉算法在以下情况更有优势:
大规模图搜索:如果图的规模很大,双向迪杰斯特拉算法很可能会比迪杰斯特拉算法更快。
稀疏图:在稀疏图中,双向迪杰斯特拉算法很可能会比迪杰斯特拉算法更快。
最主要是因为双向搜索可以减少一定的搜索空间,从终点开始搜索和从起点开始搜索,相当于做了一次剪枝。
(我觉得这张图很形象的解释了为什么双向迪杰斯特拉算法能够减少搜索空间,而单项迪杰斯特拉算法在大规模图中,会越来越发散)
但是双向迪杰斯特拉还是具有减少搜索空间和更快搜索到最短路径的优点。
双向迪杰斯特拉算法最重要的是,终止条件,算法在什么时候应该终止,如何确定相遇的点是应该终止算法的。双向迪杰斯特拉算法需要维护两个队列,一个是从起点到终点方向的队列queue_from_start
和queue_from_target
,设: t o p f top_f topf是queue_from_start
优先级队列的队头元素, t o p r top_r topr是queue_from_target
优先队列的队头元素, μ \mu μ用来记录相遇点构成的路径值,初始化 μ = ∞ \mu = \infty μ=∞。在进行路径搜索的时候,当存在一条边 ( u , v ) (u,v) (u,v)满足 u u u在前向搜索中,而 v v v在反向搜索中,如果 d f ( u ) + c ( u , v ) + d r ( v ) < μ d_f(u)+c(u,v)+d_r(v) < \mu df(u)+c(u,v)+dr(v)<μ,则更新 μ \mu μ值。
终止条件: t o p f + t o p r ≥ μ top_f + top_r \ge \mu topf+topr≥μ
程序结构:
function BidirectionalDijkstra(graph, start, end)
create priority queues queueFromStart, queueFromEnd
add start to queueFromStart with priority 0
add end to queueFromEnd with priority 0
create distance maps distanceFromStart, distanceFromEnd and set all distances to infinity
set distanceFromStart[start] to 0
set distanceFromEnd[end] to 0
create parent maps parentFromStart, parentFromEnd and set all parents to null
set parentFromStart[start] to start
set parentFromEnd[end] to end
while queueFromStart and queueFromEnd are not empty
nodeFromStart = extract minimum from queueFromStart
for each neighbor of nodeFromStart
if distance through nodeFromStart to neighbor is less than distanceFromStart[neighbor]
update distanceFromStart[neighbor]
update parentFromStart[neighbor]
add neighbor to queueFromStart with priority distanceFromStart[neighbor]
nodeFromEnd = extract minimum from queueFromEnd
for each neighbor of nodeFromEnd
if distance through nodeFromEnd to neighbor is less than distanceFromEnd[neighbor]
update distanceFromEnd[neighbor]
update parentFromEnd[neighbor]
add neighbor to queueFromEnd with priority distanceFromEnd[neighbor]
if any node v is in both queueFromStart and queueFromEnd
path = shortest path from start to v according to parentFromStart
path = path + reverse of shortest path from v to end according to parentFromEnd
return path
return no path
def bidirectional_dijkstra_b(graph, start, target, i):
queue_from_start = []
hq.heappush(queue_from_start, (0.0, start))
distance_from_start = {node: float('infinity') for node in graph}
distance_from_start[start] = 0.0
parents_of_start = {start: None}
queue_from_target = []
hq.heappush(queue_from_target, (0.0, target))
distance_from_target = {node: float('infinity') for node in graph}
distance_from_target[target] = 0.0
parents_of_target = {target: None}
close_of_start = set() # 访问禁闭表
close_of_target = set() # 访问禁闭表
miu = math.inf
global node
node = None
while queue_from_start and queue_from_target:
if queue_from_start[0][0] + queue_from_target[0][0] >= miu:
return reverse_traversal(node, parents_of_start, parents_of_target)
cur_dist, cur_node = hq.heappop(queue_from_start)
close_of_start.add(cur_node)
for adjacent, weight in graph[cur_node].items():
if adjacent in close_of_start:
continue
distance = cur_dist + weight["weight"]
if distance < distance_from_start[adjacent]:
distance_from_start[adjacent] = distance
parents_of_start[adjacent] = cur_node
hq.heappush(queue_from_start, (distance, adjacent))
# 更新miu值
if adjacent in close_of_target:
dist = distance + distance_from_target[adjacent]
if miu > dist:
miu = dist
node = adjacent
cur_dist, cur_node = hq.heappop(queue_from_target)
close_of_target.add(cur_node)
for adjacent, weight in graph[cur_node].items():
if adjacent in close_of_target:
continue
distance = cur_dist + weight["weight"]
if distance < distance_from_target[adjacent]:
distance_from_target[adjacent] = distance
parents_of_target[adjacent] = cur_node
hq.heappush(queue_from_target, (distance, adjacent))
if adjacent in close_of_start:
dist = distance + distance_from_start[adjacent]
if miu > dist:
miu = dist
node = adjacent
return []
双向迪杰斯特拉算法写了两个版本,主要是控制方式不同。
实验中生成稀疏图标准是, e = n ∗ log n e=n*\log{n} e=n∗logn
可以看出,随着节点和边的数量增多,算法的耗时越大,在节点数=50000,边数=252959时,迪杰斯特拉算法的速度远远高于双向迪杰斯特拉算法。
从上图来看,在50,500数量级的节点数下,第二种控制流的迪杰斯特拉算法更快,但是随着节点数数量级增大,第一种控制流双向迪杰斯特拉算法更优。推测结论:双向迪杰斯特拉算法是内含分治法思想的,如果两侧探索数量越均等越好。