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

本文介绍了双向迪杰斯特拉算法的工作原理、优势(如大规模图和稀疏图中的效率提升)、局限性(如复杂性和对特定图型的适用性)以及Python实现。对比了双向迪杰斯特拉与单向迪杰斯特拉在不同规模图中的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

双向迪杰斯特拉算法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数量级的节点数下,第二种控制流的迪杰斯特拉算法更快,但是随着节点数数量级增大,第一种控制流双向迪杰斯特拉算法更优。推测结论:双向迪杰斯特拉算法是内含分治法思想的,如果两侧探索数量越均等越好。

### Dijkstra算法Python实现 Dijkstra算法用于计算加权图中的单源最短路径问题。该算法假设所有的边权重都是正数[^3]。 下面是一个基于优先队列优化版本的Dijkstra算法实现: ```python import heapq def dijkstra(graph, start): # 初始化距离字典,默认值为无穷大 distances = {node: float('infinity') for node in graph} distances[start] = 0 # 创建一个最小堆,存储待处理节点及其当前已知最短距离 priority_queue = [(0, start)] while priority_queue: current_distance, current_node = heapq.heappop(priority_queue) # 如果弹出的距离大于记录的距离,则跳过此轮循环 if current_distance > distances[current_node]: continue # 遍历邻居节点并更新其距离 for neighbor, weight in graph[current_node].items(): distance = current_distance + weight # 只有当找到更短路径时才进行更新操作 if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(priority_queue, (distance, neighbor)) return distances ``` 在这个例子中,`graph` 是邻接表表示法下的图结构,其中键是顶点名称而值是由相邻顶点组成的列表以及对应的边权重;`start` 表示起始顶点。函数返回的是从起点到其他各点之间的最短路径长度构成的一个映射关系 `distances`。 为了更好地理解上述代码的工作原理,可以考虑如下测试用例来验证其实现效果: ```python if __name__ == "__main__": test_graph = { 'A': {'B': 1, 'C': 4}, 'B': {'A': 1, 'C': 2, 'D': 5}, 'C': {'A': 4, 'B': 2, 'D': 1}, 'D': {'B': 5, 'C': 1} } result = dijkstra(test_graph, 'A') print(result) ``` 这段程序会输出从 `'A'` 到其余各个结点间的最短路径长度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SUNX-T

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

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

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

打赏作者

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

抵扣说明:

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

余额充值