【Python 算法】双向迪杰斯特拉算法 Python实现

双向迪杰斯特拉算法Python实现

简介

双向迪杰斯特拉算法(Bi Directional Dijkstra Algorithm)是一种用于在加权图中查找两个顶点之间最短路径的算法,是Dijkstra算法的一个变种,基本思想是:从两个搜索方向同时开始搜索——从起点到终点方向和从终点到起点方向同时进行迪杰斯特拉算法搜索,如果存在路径,那么最终两个方向的搜索会在某点相遇并终止,而这条路径就是最短距离路径。在某些情况下,双向迪杰斯特拉算法可以减少搜索空间大小,从而提高算法效率。其中也有分治法的思想

双向迪杰斯特拉算法优势

与迪杰斯特拉算法相比,双向迪杰斯特拉算法在以下情况更有优势:

  1. 大规模图搜索:如果图的规模很大,双向迪杰斯特拉算法很可能会比迪杰斯特拉算法更快。

  2. 稀疏图:在稀疏图中,双向迪杰斯特拉算法很可能会比迪杰斯特拉算法更快。

最主要是因为双向搜索可以减少一定的搜索空间,从终点开始搜索和从起点开始搜索,相当于做了一次剪枝。

image-20231113185857959

(我觉得这张图很形象的解释了为什么双向迪杰斯特拉算法能够减少搜索空间,而单项迪杰斯特拉算法在大规模图中,会越来越发散)

局限性

  1. 复杂性:双向迪杰斯特拉算法需要更复杂的数据结构来跟踪记录两个方向的搜索路径。算法需要更多时间维护两个方向的队列。
  2. 权重不均等的图、负权重图:对于边权重差异比较大和负权重的图,双向迪杰斯特拉算法可能不会表现得很好。
  3. 当终点和起点距离比较近的时候,双向迪杰斯特拉算法算法可能不如单项迪杰斯特拉算法算法。

但是双向迪杰斯特拉还是具有减少搜索空间更快搜索到最短路径的优点。

算法的基本步骤

终止条件

双向迪杰斯特拉算法最重要的是,终止条件,算法在什么时候应该终止,如何确定相遇的点是应该终止算法的。双向迪杰斯特拉算法需要维护两个队列,一个是从起点到终点方向的队列queue_from_startqueue_from_target,设: t o p f top_f topfqueue_from_start优先级队列的队头元素, t o p r top_r toprqueue_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μ

双向迪杰斯特拉算法

基本步骤

  1. 初始化:将起点加入到正向搜索的待处理队列中,将终点加入到反向搜索的待处理队列中。
  2. 迭代搜索:在每一次迭代中,算法分别对正向和反向的待处理队列中的顶点进行处理,选择当前距离最小的顶点进行扩展。
  3. 扩展顶点:对于选中的顶点,算法更新其邻接顶点的最短路径估计,并将这些邻接顶点加入到相应的待处理集合中。
  4. 检查相遇:在每次扩展后,算法检查正向和反向的搜索是否在某个顶点上相遇。相遇的条件通常是检查某个顶点是否同时出现在正向和反向的访问表中。
  5. 路径重构:一旦搜索相遇,算法使用正向和反向的路径信息来重构出从起点到终点的最短路径。
  6. 终止条件:如果两个搜索在中间某处相遇,或者一个方向的搜索已经找不到新的顶点进行扩展,算法终止。

伪代码

程序结构:

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
	


Python 实现

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=nlogn

test_result1 test_result

可以看出,随着节点和边的数量增多,算法的耗时越大,在节点数=50000,边数=252959时,迪杰斯特拉算法的速度远远高于双向迪杰斯特拉算法。

从上图来看,在50,500数量级的节点数下,第二种控制流的迪杰斯特拉算法更快,但是随着节点数数量级增大,第一种控制流双向迪杰斯特拉算法更优。推测结论:双向迪杰斯特拉算法是内含分治法思想的,如果两侧探索数量越均等越好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SUNX-T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值