数学建模优秀论文算法-实时动态网络的dijkstra算法

实时动态网络的Dijkstra算法入门教程

1. 背景溯源:从静态到动态的必然需求

要理解实时动态网络的Dijkstra算法 ,我们需要先回顾它的"前身"------静态Dijkstra算法的局限性。

1.1 静态Dijkstra的核心场景

静态Dijkstra算法是1959年由荷兰计算机科学家Edsger W. Dijkstra提出的,用于解决固定网络 中的单源最短路径问题(Single-Source Shortest Path, SSSP)。这里的"固定网络"指:

  • 节点集VVV和边集EEE不随时间变化;
  • 每条边的权重w(u,v)w(u,v)w(u,v)(如距离、时间、成本)是恒定值

典型场景包括:计算地图上两个固定地点的最短路径(假设路况永远不变)、物流网络中固定节点间的最优运输路线(假设道路容量永远充足)。

1.2 动态网络的现实挑战

但现实世界的网络几乎都是动态变化的:

  • 交通网络:早高峰时某条路的通行时间从10分钟增加到30分钟(边权增加),临时修路导致某条路封闭(边删除),新开通一条快速路(边添加);
  • 物流网络:某个仓库的货物积压导致中转时间变长(节点权增加);
  • 通信网络:某条链路的带宽下降导致传输延迟增加(边权增加)。

静态Dijkstra的问题在于:每次网络变化后,必须重新计算所有节点的最短路径,这在实时场景中完全不可行(比如导航App无法让用户等10秒重新计算路线)。

1.3 动态Dijkstra的诞生

为了解决"实时性"问题,研究者对静态Dijkstra进行了扩展,提出实时动态网络的Dijkstra算法(Dynamic Dijkstra Algorithm)。其核心目标是:

当网络发生动态变化时,不重新计算全部路径 ,而是基于之前的最短路径结果,增量式更新受影响的部分,从而快速恢复正确的最短路径。

2. 核心思想:增量维护 vs 全盘重算

动态Dijkstra的核心思想可以总结为两句话:

  1. 预处理基础 :先运行静态Dijkstra得到初始的最短路径树(Shortest Path Tree, SPT)最短距离数组 dist(v)dist(v)dist(v)(表示源点sss到节点vvv的最短距离);
  2. 动态更新 :当网络发生变化(如边权增减、边添加/删除)时,仅修改受影响的节点 的dist(v)dist(v)dist(v)和SPT结构,而非重新计算所有节点。

2.1 关键概念铺垫

在展开算法前,先明确几个动态网络的基础定义:

  • 动态网络 :随时间ttt变化的图G(t)=(V,E(t),w(t))G(t) = (V, E(t), w(t))G(t)=(V,E(t),w(t)),其中:
    • VVV:固定节点集(如城市中的路口,不会突然消失);
    • E(t)E(t)E(t):时刻ttt的边集(如某条路在ttt时刻是否通行);
    • w(t)w(t)w(t):时刻ttt的边权函数,满足w(t)(u,v)≥0w(t)(u,v) \geq 0w(t)(u,v)≥0(非负性,Dijkstra算法的核心前提)。
  • 动态事件 :导致网络变化的"触发条件",主要分为三类:
    1. 边权变化 :wnew(u,v)eqwold(u,v)w_{new}(u,v) eq w_{old}(u,v)wnew(u,v)eqwold(u,v)(如路况变好/变坏);
    2. 边添加 :新增一条边(u,v)(u,v)(u,v)(如开通新路);
    3. 边删除 :移除一条边(u,v)(u,v)(u,v)(如道路封闭)。
  • 最短路径树(SPT) :以源点sss为根的树结构,满足:
    • 树中任意节点vvv的路径s→⋯→vs \to \dots \to vs→⋯→v是G(t)G(t)G(t)中sss到vvv的最短路径;
    • 树的边集是原网络边集的子集(ESPT⊆E(t)E_{SPT} \subseteq E(t)ESPT⊆E(t))。

3. 算法原理:动态事件的分类处理

动态Dijkstra的核心是针对不同类型的动态事件,设计高效的增量更新规则 。由于边的添加/删除可视为"边权从无穷→有限"或"有限→无穷"的特殊情况,我们重点讲解边权变化的处理逻辑(最常见的动态事件)。

3.1 前提假设

为简化问题,我们做以下合理假设(符合绝大多数现实场景):

  • 网络是有向图(如道路是单行道,通信链路是单向传输);
  • 边权非负 (w(t)(u,v)≥0w(t)(u,v) \geq 0w(t)(u,v)≥0,Dijkstra算法的基础条件);
  • 源点sss固定(如导航App中用户的当前位置);
  • 初始状态已通过静态Dijkstra得到:
    • 最短距离数组distold(v)dist_{old}(v)distold(v)(distold(s)=0dist_{old}(s)=0distold(s)=0,其他节点为无穷大∞\infty∞);
    • 最短路径树的父节点数组prev(v)prev(v)prev(v)(prev(v)prev(v)prev(v)表示sss到vvv的最短路径中vvv的前一个节点)。

3.2 动态事件的两类情况

边权变化可分为边权减少 (wnew(u,v)<wold(u,v)w_{new}(u,v) < w_{old}(u,v)wnew(u,v)<wold(u,v))和边权增加 (wnew(u,v)>wold(u,v)w_{new}(u,v) > w_{old}(u,v)wnew(u,v)>wold(u,v)),两者的处理逻辑完全不同------因为:

  • 边权减少可能带来更短的路径(需要"寻找改进");
  • 边权增加可能破坏原有的最短路径(需要"修复破坏")。
3.2.1 情况1:边权减少(wnew(u,v)<wold(u,v)w_{new}(u,v) < w_{old}(u,v)wnew(u,v)<wold(u,v))

核心逻辑:边权减少后,原SPT中可能存在"更短的路径",需要用**松弛操作(Relaxation)**扩散这些改进。

松弛操作是Dijkstra算法的核心,定义为:对边u→vu \to vu→v,检查是否可以通过uuu来缩短sss到vvv的距离:

$

dist(v) = \min\left( dist(v),\ dist(u) + w(u,v) \right)

$

具体步骤 (以边u→vu \to vu→v的权从woldw_{old}wold降到wneww_{new}wnew为例):

  1. 尝试松弛边u→vu \to vu→v :计算新的可能距离candidate=distold(u)+wnew(u,v)candidate = dist_{old}(u) + w_{new}(u,v)candidate=distold(u)+wnew(u,v);
  2. 判断是否有效 :如果candidate<distold(v)candidate < dist_{old}(v)candidate<distold(v),说明这条边的权减少带来了更短的路径,需要更新:
    • 将dist(v)dist(v)dist(v)更新为candidatecandidatecandidate(distnew(v)=candidatedist_{new}(v) = candidatedistnew(v)=candidate);
    • 将prev(v)prev(v)prev(v)更新为uuu(prevnew(v)=uprev_{new}(v) = uprevnew(v)=u);
  3. 扩散改进 :由于vvv的距离变短,其所有邻接节点(即vvv指向的节点xxx,边v→xv \to xv→x)的距离可能也会变短,因此需要:
    • 将vvv加入优先队列(Priority Queue) (按distdistdist值从小到大排序,确保优先处理距离最短的节点);
    • 重复松弛操作:从优先队列中取出距离最小的节点xxx,对其所有邻接节点yyy执行松弛,直到优先队列为空。

例子说明(简化的交通网络):

  • 初始状态:源点sss到节点aaa的最短路径是s→b→as \to b \to as→b→a,边权w(s,b)=2w(s,b)=2w(s,b)=2,w(b,a)=3w(b,a)=3w(b,a)=3,总距离distold(a)=5dist_{old}(a)=5distold(a)=5;
  • 动态事件:w(b,a)w(b,a)w(b,a)从3降到1(路况变好);
  • 处理过程:
    1. 松弛边b→ab \to ab→a:candidate=distold(b)+1=2+1=3<5candidate = dist_{old}(b) + 1 = 2 + 1 = 3 < 5candidate=distold(b)+1=2+1=3<5,更新dist(a)=3dist(a)=3dist(a)=3;
    2. 将aaa加入优先队列;
    3. 取出aaa,松弛其邻接节点(如a→ca \to ca→c,假设原dist(c)=10dist(c)=10dist(c)=10,w(a,c)=2w(a,c)=2w(a,c)=2):新距离3+2=5<103 + 2 = 5 < 103+2=5<10,更新dist(c)=5dist(c)=5dist(c)=5;
    4. 将ccc加入优先队列,继续松弛直到队列空。
3.2.2 情况2:边权增加(wnew(u,v)>wold(u,v)w_{new}(u,v) > w_{old}(u,v)wnew(u,v)>wold(u,v))

核心逻辑 :边权增加后,原SPT中所有依赖这条边的路径 可能不再是最短路径,需要定位并修复这些被破坏的节点

关键难点:如何快速找到"依赖这条边的节点"------即原SPT中最短路径经过u→vu \to vu→v的节点 。这些节点的集合记为AAA,我们需要对AAA中的节点重新计算最短路径。

具体步骤 (以边u→vu \to vu→v的权从woldw_{old}wold升到wneww_{new}wnew为例):

  1. 定位受影响的节点集合AAA
    • 原SPT中,vvv的最短路径来自uuu(即prevold(v)=uprev_{old}(v) = uprevold(v)=u),否则这条边的权增加不影响任何节点(因为原SPT中没有使用这条边);
    • 如果prevold(v)=uprev_{old}(v) = uprevold(v)=u,则所有能从vvv出发到达的节点 (在原SPT中)都依赖这条边------因为它们的最短路径需要经过vvv,而vvv的路径需要经过u→vu \to vu→v;
    • 如何找到这些节点?用反向SPT :将原SPT的边方向反转(如v←uv \leftarrow uv←u变为u→vu \to vu→v),从vvv出发进行遍历(如广度优先搜索BFS),所有能到达的节点即为集合AAA。
  2. 重置受影响节点的距离
    • 将AAA中所有节点的distdistdist值重置为无穷大(distnew(x)=∞dist_{new}(x) = \inftydistnew(x)=∞,x∈Ax \in Ax∈A);
    • 注意:源点sss的distdistdist始终为0,不能重置。
  3. 重新计算最短路径
    • 使用优先队列 对整个网络重新执行Dijkstra算法,但仅处理受影响的节点?不------实际上,我们需要将所有节点(包括非AAA中的节点)的当前distdistdist值作为初始状态,然后从优先队列中取出节点进行松弛,直到队列空。
    • 但由于AAA外的节点的distdistdist值未被破坏,优先队列会优先处理这些节点,因此实际计算量很小。

例子说明(延续之前的交通网络):

  • 初始状态:s→b→as \to b \to as→b→a的总距离是5(w(s,b)=2w(s,b)=2w(s,b)=2,w(b,a)=3w(b,a)=3w(b,a)=3),且prev(a)=bprev(a)=bprev(a)=b(aaa的路径来自bbb);
  • 动态事件:w(b,a)w(b,a)w(b,a)从3升到6(路况变差);
  • 处理过程:
    1. 定位受影响的节点:因为prev(a)=bprev(a)=bprev(a)=b,所以从aaa出发遍历反向SPT,得到集合A={a,c}A = \{a, c\}A={a,c}(假设ccc的路径是s→b→a→cs \to b \to a \to cs→b→a→c);
    2. 重置dist(a)=∞dist(a) = \inftydist(a)=∞,dist(c)=∞dist(c) = \inftydist(c)=∞;
    3. 重新执行Dijkstra:
      • 优先队列初始包含所有节点(dist(s)=0dist(s)=0dist(s)=0,dist(b)=2dist(b)=2dist(b)=2,dist(a)=∞dist(a)=\inftydist(a)=∞,dist(c)=∞dist(c)=\inftydist(c)=∞);
      • 取出sss,松弛其邻接节点bbb(dist(b)=2dist(b)=2dist(b)=2,无变化);
      • 取出bbb,松弛其邻接节点aaa:新距离2+6=82 + 6 = 82+6=8,所以dist(a)=8dist(a)=8dist(a)=8;
      • 取出aaa,松弛其邻接节点ccc:新距离8+2=108 + 2 = 108+2=10,所以dist(c)=10dist(c)=10dist(c)=10;
      • 队列空,更新完成。
3.2.3 情况3:边添加/删除(特殊的边权变化)
  • 边添加 (新增边u→vu \to vu→v,权为www):相当于边权从∞\infty∞降到www,处理逻辑同边权减少------尝试松弛这条边,然后扩散改进。
  • 边删除 (移除边u→vu \to vu→v):相当于边权从www升到∞\infty∞,处理逻辑同边权增加------定位所有依赖这条边的节点,重置其距离,重新计算。

4. 完整模型求解步骤

现在,我们将动态Dijkstra的完整流程总结为5步,覆盖从初始化到动态更新的全生命周期:

步骤1:初始化静态网络

  • 给定动态网络的初始状态G(0)=(V,E(0),w(0))G(0) = (V, E(0), w(0))G(0)=(V,E(0),w(0)),源点sss;
  • 运行静态Dijkstra算法 ,得到初始的:
    • 最短距离数组dist(0)dist(0)dist(0)(dist(0)(s)=0dist(0)(s)=0dist(0)(s)=0,其他节点为∞\infty∞);
    • 父节点数组prev(0)prev(0)prev(0)(记录每个节点的前一个节点);
    • 最短路径树SPT(0)SPT(0)SPT(0)(由prev(0)prev(0)prev(0)构成)。

步骤2:监听动态事件

  • 实时监控网络状态,当发生动态事件(如边权变化、边添加/删除)时,触发更新流程。

步骤3:分类处理动态事件

根据事件类型,执行对应的处理逻辑(参考3.2节):

  • 边权减少:尝试松弛该边,扩散改进;
  • 边权增加:定位受影响节点,重置距离,重新计算;
  • 边添加 :视为边权从∞\infty∞降到当前值,执行边权减少的逻辑;
  • 边删除 :视为边权从当前值升到∞\infty∞,执行边权增加的逻辑。

步骤4:更新状态

  • 根据处理结果,更新:
    • 最新的最短距离数组dist(t)dist(t)dist(t)(ttt为当前时刻);
    • 最新的父节点数组prev(t)prev(t)prev(t);
    • 最新的最短路径树SPT(t)SPT(t)SPT(t)。

步骤5:返回结果

  • 向用户返回最新的最短路径(如导航App显示"当前最短路径为s→b→c→ds \to b \to c \to ds→b→c→d");
  • 等待下一个动态事件,重复步骤2-4。

5. 适用边界:什么时候用动态Dijkstra?

动态Dijkstra不是"万能算法",它的优势仅在特定场景下才能发挥------以下是它的适用条件和局限性:

5.1 适用条件

  1. 边权非负:这是Dijkstra算法的核心前提,若存在负权边(如"反向行驶的奖励"),动态Dijkstra会失效(需用Bellman-Ford的动态版本);
  2. 动态事件频率适中:如果事件频率过高(如每秒发生100次边权变化),每次更新的成本会超过重新跑静态Dijkstra的成本;
  3. 动态变化幅度小:边权变化的幅度越小,受影响的节点数量越少,更新效率越高(如边权从10增加到12,比从10增加到100的影响小得多);
  4. 需要实时响应:如导航系统、实时物流调度、通信网络路由------这些场景要求"毫秒级"的响应速度,无法等待全盘重算;
  5. 源点固定:动态Dijkstra针对"单源"问题设计,若源点频繁变化(如同时处理1000个用户的导航请求),则需要其他算法(如多源动态最短路径算法)。

5.2 局限性

  1. 无法处理负权边:若边权可能为负(如"折扣路径"),动态Dijkstra会给出错误结果;
  2. 处理边权增加的效率较低:定位受影响节点的过程需要遍历反向SPT,若受影响节点数量大(如整个网络的节点都依赖这条边),效率会比静态Dijkstra更低;
  3. 不适用于无向图的双向变化 :无向图中边u−vu-vu−v的权变化相当于两条有向边u→vu \to vu→v和v→uv \to uv→u的权变化,处理逻辑更复杂;
  4. 依赖初始SPT的正确性:若初始SPT错误(如静态Dijkstra运行时出现bug),后续所有动态更新的结果都会错误。

6. 总结:动态Dijkstra的本质

实时动态网络的Dijkstra算法,本质是用"增量维护"替代"全盘重算",通过利用之前的计算结果,避免重复计算未受影响的节点。它的核心价值在于:

  • 提升实时性:将响应时间从"秒级"缩短到"毫秒级";
  • 降低计算成本:减少不必要的重复计算,节省算力;
  • 适应现实场景:解决静态Dijkstra无法处理的动态网络问题。

附录:常见问题解答

Q1:动态Dijkstra和静态Dijkstra的时间复杂度有什么区别?

  • 静态Dijkstra的时间复杂度(用斐波那契堆):O(M+Nlog⁡N)O(M + N \log N)O(M+NlogN)(NNN为节点数,MMM为边数);
  • 动态Dijkstra的时间复杂度:取决于动态事件的类型和影响范围:
    • 边权减少:O(klog⁡N)O(k \log N)O(klogN)(kkk为受影响的节点数);
    • 边权增加:O(klog⁡N+L)O(k \log N + L)O(klogN+L)(LLL为反向SPT的遍历时间);
    • 若k≪Nk \ll Nk≪N(受影响节点远少于总节点),动态算法的效率远高于静态。

Q2:如果网络中同时发生多个动态事件(如两条边的权同时减少),该怎么办?

  • 按顺序处理每个事件:先处理第一个事件,更新状态后,再处理第二个事件;
  • 若事件之间相互独立(如两条边没有共享节点),可以并行处理,但大多数场景下事件是关联的,需顺序处理。

Q3:动态Dijkstra能处理节点权的变化吗?

  • 能!节点权(如节点的中转时间)可以转化为边权:将节点vvv的权c(v)c(v)c(v)拆分为两条边:vin→voutv_{in} \to v_{out}vin→vout,权为c(v)c(v)c(v),然后将所有进入vvv的边指向vinv_{in}vin,所有从vvv出发的边从voutv_{out}vout出发。这样,节点权的变化就转化为边权的变化,用动态Dijkstra处理即可。

通过以上内容,相信你已经掌握了实时动态网络Dijkstra算法的核心逻辑。接下来,可以尝试用小例子(如模拟交通路况变化)手动推导,加深理解------实践是掌握算法的最佳途径!

python 复制代码
import heapq

# 初始化静态网络
def init_static_dijkstra(graph, source):
    n = len(graph)
    dist = [float('inf')] * n  # 最短距离数组
    prev = [-1] * n  # 父节点数组,记录路径
    dist[source] = 0
    heap = [(0, source)]  # 优先队列,(距离, 节点)

    while heap:
        current_dist, u = heapq.heappop(heap)
        # 如果当前距离大于已知最短距离,跳过
        if current_dist > dist[u]:
            continue
        # 遍历所有邻接节点
        for v, w in graph[u]:
            if dist[v] > dist[u] + w:
                dist[v] = dist[u] + w
                prev[v] = u
                heapq.heappush(heap, (dist[v], v))
    return dist, prev

# 处理边权减少事件
def handle_edge_decrease(graph, dist, prev, u, v, new_w):
    # 更新图中的边权
    for i, (edge_v, edge_w) in enumerate(graph[u]):
        if edge_v == v:
            graph[u][i] = (v, new_w)
            break
    # 尝试松弛边 u->v
    if dist[u] + new_w < dist[v]:
        dist[v] = dist[u] + new_w
        prev[v] = u
        # 优先队列
        heap = [(dist[v], v)]
        # 扩散改进
        while heap:
            current_dist, current_node = heapq.heappop(heap)
            if current_dist > dist[current_node]:
                continue
            # 遍历邻接节点
            for neighbor, w in graph[current_node]:
                if dist[neighbor] > dist[current_node] + w:
                    dist[neighbor] = dist[current_node] + w
                    prev[neighbor] = current_node
                    heapq.heappush(heap, (dist[neighbor], neighbor))
    return graph, dist, prev

# 处理边权增加事件
def handle_edge_increase(graph, dist, prev, u, v, new_w):
    # 更新图中的边权
    for i, (edge_v, edge_w) in enumerate(graph[u]):
        if edge_v == v:
            graph[u][i] = (v, new_w)
            break
    # 检查原路径是否使用该边
    if prev[v] != u:
        return graph, dist, prev  # 未使用,无需更新
    # 定位受影响的节点集合 A
    n = len(graph)
    visited = [False] * n
    A = []
    # 从 v 出发遍历反向路径树,找到所有依赖该边的节点
    stack = [v]
    while stack:
        current_node = stack.pop()
        if visited[current_node]:
            continue
        visited[current_node] = True
        A.append(current_node)
        # 遍历所有邻接节点,找到以 current_node 为父节点的节点
        for neighbor in range(n):
            if prev[neighbor] == current_node:
                stack.append(neighbor)
    # 重置 A 中节点的距离为无穷大
    for node in A:
        dist[node] = float('inf')
    # 重新执行 Dijkstra 算法
    heap = [(dist[0], 0)]  # 假设源点是 0
    while heap:
        current_dist, current_node = heapq.heappop(heap)
        if current_dist > dist[current_node]:
            continue
        for neighbor, w in graph[current_node]:
            if dist[neighbor] > dist[current_node] + w:
                dist[neighbor] = dist[current_node] + w
                prev[neighbor] = current_node
                heapq.heappush(heap, (dist[neighbor], neighbor))
    return graph, dist, prev

# 处理边添加事件
def handle_edge_add(graph, dist, prev, u, v, new_w):
    # 将边添加到图中
    graph[u].append((v, new_w))
    # 视为边权从无穷大降到 new_w,执行边权减少的逻辑
    return handle_edge_decrease(graph, dist, prev, u, v, new_w)

# 处理边删除事件
def handle_edge_delete(graph, dist, prev, u, v):
    # 将边权设为无穷大,执行边权增加的逻辑
    return handle_edge_increase(graph, dist, prev, u, v, float('inf'))

# 主程序
if __name__ == "__main__":
    # 案例:模拟城市交通网络(节点 0-4 代表路口)
    # 初始图:graph[u] = [(v, weight), ...],有向图
    initial_graph = [
        [(1, 2), (2, 5)],  # 0的邻接节点:0->1(2), 0->2(5)
        [(2, 1), (3, 4)],  # 1的邻接节点:1->2(1), 1->3(4)
        [(3, 2), (4, 6)],  # 2的邻接节点:2->3(2), 2->4(6)
        [(4, 3)],           # 3的邻接节点:3->4(3)
        []                  # 4的邻接节点:无
    ]
    source = 0  # 源点为 0

    # 初始化静态Dijkstra
    dist, prev = init_static_dijkstra(initial_graph, source)
    print("初始最短距离:", dist)  # 应输出:[0, 2, 3, 5, 8]

    # 动态事件1:边 1->3 的权从 4 减少到 2(路况变好)
    print("
动态事件1:边 1->3 权减少到 2")
    updated_graph, updated_dist, updated_prev = handle_edge_decrease(initial_graph, dist.copy(), prev.copy(), 1, 3, 2)
    print("更新后最短距离:", updated_dist)  # 应输出:[0, 2, 3, 4, 7]

    # 动态事件2:边 2->3 的权从 2 增加到 5(路况变差)
    print("
动态事件2:边 2->3 权增加到 5")
    updated_graph2, updated_dist2, updated_prev2 = handle_edge_increase(updated_graph, updated_dist.copy(), updated_prev.copy(), 2, 3, 5)
    print("更新后最短距离:", updated_dist2)  # 应输出:[0, 2, 3, 4, 7](因为当前最短路径不经过该边)

    # 动态事件3:新增边 0->4,权为 7
    print("
动态事件3:新增边 0->4,权为 7")
    updated_graph3, updated_dist3, updated_prev3 = handle_edge_add(updated_graph2, updated_dist2.copy(), updated_prev2.copy(), 0, 4, 7)
    print("更新后最短距离:", updated_dist3)  # 应输出:[0, 2, 3, 4, 7]

代码整体框架:动态单源最短路径(Dynamic SSSP)维护系统

这段代码实现了支持边权动态修改(增/减)、边增/删操作 的Dijkstra算法变种,用于实时维护从固定源点到所有节点的最短路径 。核心思想是增量更新而非全量重跑静态Dijkstra,大幅降低动态场景下的时间复杂度,常用于交通网络、网络路由、物流调度等需要实时更新路径成本的数学建模场景。


模块1:静态Dijkstra初始化(init_static_dijkstra

核心作用:计算初始图的单源最短路径,为后续动态更新提供基础

python 复制代码
import heapq  # Python内置小顶堆,用于Dijkstra的贪心选择

def init_static_dijkstra(graph, source):
    n = len(graph)  # 节点总数(节点编号0~n-1)
    # 1. 初始化距离数组:dist[i] = 源点到i的当前最短距离(初始为无穷大)
    dist = [float('inf')] * n
    # 2. 初始化前驱数组:prev[i] = 最短路径中i的前一个节点(用于路径回溯)
    prev = [-1] * n
    # 3. 源点到自身距离为0
    dist[source] = 0
    # 4. 小顶堆优先队列:存储 (当前距离, 节点),每次取距离最小的未处理节点
    heap = [(0, source)]

    while heap:
        # 弹出当前最短距离的节点(贪心选择)
        current_dist, u = heapq.heappop(heap)
        # 【优化】:如果当前距离>已知最短距离,说明是旧的无效记录,跳过
        if current_dist > dist[u]:
            continue
        # 遍历u的所有邻接节点(v, w):w为u->v的边权
        for v, w in graph[u]:
            # 松弛操作:若经u到v的距离比当前dist[v]短,则更新
            if dist[v] > dist[u] + w:
                dist[v] = dist[u] + w  # 更新最短距离
                prev[v] = u  # 更新前驱节点
                # 将新的距离-节点对加入堆(允许重复,靠上面的优化跳过无效值)
                heapq.heappush(heap, (dist[v], v))
    return dist, prev  # 返回初始最短距离和前驱树
关键数学逻辑:
  • 邻接列表存储graph[u] = [(v, w), ...] 表示有向图中节点u的所有出边,时间复杂度O(n+m)(n为节点数,m为边数)
  • Dijkstra贪心策略:每次选择当前距离源点最近的未处理节点,确保该节点的最短距离已确定(仅适用于非负边权!这是代码的前置条件)
  • 堆优化:将时间复杂度从O(n²)降至O(m log n),是大规模图的必备优化

模块2:边权减少事件处理(handle_edge_decrease

核心作用:处理u->v边权从旧值减小到new_w的场景(如路况变好、链路延迟降低)

数学直觉:边权减少只会让最短路径不变或更短,不会变长,因此只需增量更新受影响的节点
python 复制代码
def handle_edge_decrease(graph, dist, prev, u, v, new_w):
    # 1. 先更新原图的边权:在u的邻接列表中找到v,替换为新权值
    for i, (edge_v, edge_w) in enumerate(graph[u]):
        if edge_v == v:
            graph[u][i] = (v, new_w)
            break
    # 2. 松弛更新后的边u->v:若经u到v的新距离更短,则启动扩散更新
    if dist[u] + new_w < dist[v]:
        dist[v] = dist[u] + new_w  # 更新v的最短距离
        prev[v] = u  # 更新v的前驱为u
        # 3. 局部小顶堆:从v出发扩散更新所有依赖v的节点
        heap = [(dist[v], v)]
        while heap:
            current_dist, current_node = heapq.heappop(heap)
            if current_dist > dist[current_node]:
                continue  # 同静态Dijkstra的旧记录优化
            # 遍历current_node的所有邻接节点,松弛更新
            for neighbor, w in graph[current_node]:
                if dist[neighbor] > dist[current_node] + w:
                    dist[neighbor] = dist[current_node] + w
                    prev[neighbor] = current_node
                    heapq.heappush(heap, (dist[neighbor], neighbor))
    return graph, dist, prev  # 返回更新后的图、距离、前驱树
关键优化:
  • 仅从v开始扩散更新,因为只有以v为中间节点的路径才可能因v的距离缩短而变短,避免全量重跑

模块3:边权增加事件处理(handle_edge_increase

核心作用:处理u->v边权从旧值增大到new_w的场景(如路况变差、链路拥堵)

数学直觉:边权增加可能让原来经过u->v的最短路径失效,需找到所有依赖该边的节点并重新计算
python 复制代码
def handle_edge_increase(graph, dist, prev, u, v, new_w):
    # 1. 先更新原图的边权:同边权减少
    for i, (edge_v, edge_w) in enumerate(graph[u]):
        if edge_v == v:
            graph[u][i] = (v, new_w)
            break
    # 2. 【关键检查】:原最短路径树中,v的前驱是否是u?
    # 若不是,说明该边从未被用于最短路径,无需更新,直接返回
    if prev[v] != u:
        return graph, dist, prev
    # 3. 找到**所有依赖u->v的节点集合A**:从v出发,遍历**反向前驱树**
    n = len(graph)
    visited = [False] * n  # 标记已访问的节点
    A = []  # 存储所有受影响的节点
    stack = [v]  # 用栈实现深度优先遍历(DFS)
    while stack:
        current_node = stack.pop()
        if visited[current_node]:
            continue
        visited[current_node] = True
        A.append(current_node)  # 加入受影响集合
        # 遍历所有节点,找到以current_node为前驱的节点(即依赖current_node的节点)
        for neighbor in range(n):
            if prev[neighbor] == current_node:
                stack.append(neighbor)
    # 4. 重置A中所有节点的距离为无穷大(无效化旧路径)
    for node in A:
        dist[node] = float('inf')
    # 5. 重新执行Dijkstra算法,但仅需处理无效化的节点
    # 【注意】:代码此处**硬编码源点为0**,若源点变化需修改!
    heap = [(dist[0], 0)]
    while heap:
        current_dist, current_node = heapq.heappop(heap)
        if current_dist > dist[current_node]:
            continue
        for neighbor, w in graph[current_node]:
            if dist[neighbor] > dist[current_node] + w:
                dist[neighbor] = dist[current_node] + w
                prev[neighbor] = current_node
                heapq.heappush(heap, (dist[neighbor], neighbor))
    return graph, dist, prev
关键逻辑:
  • 反向前驱树遍历 :prev数组构成了一棵以源点为根的最短路径树,prev[neighbor]=current_node表示neighbor的最短路径必须经过current_node,因此从v出发反向遍历可精准定位所有依赖u->v的节点
  • 局部重算:仅重置A中节点的距离,重新跑Dijkstra时,非A节点的距离已正确,堆会优先处理这些节点,等价于局部重算,效率远高于全量重跑

模块4:边添加/删除事件处理(复用已有逻辑)

核心思想:将边的增/删转化为已实现的边权减/增逻辑,避免重复代码

python 复制代码
# 处理边添加:新增u->v边(权为new_w)= 该边原权为无穷大,现在减小到new_w
def handle_edge_add(graph, dist, prev, u, v, new_w):
    graph[u].append((v, new_w))  # 先将边加入原图
    return handle_edge_decrease(graph, dist, prev, u, v, new_w)  # 复用边权减少逻辑

# 处理边删除:删除u->v边 = 该边权从旧值增大到无穷大
def handle_edge_delete(graph, dist, prev, u, v):
    return handle_edge_increase(graph, dist, prev, u, v, float('inf'))  # 复用边权增加逻辑

模块5:主程序(案例验证)

场景:模拟5个路口(0~4)的交通网络,动态更新路况

python 复制代码
if __name__ == "__main__":
    # 初始有向图:graph[u] = [(v, weight)]
    initial_graph = [
        [(1, 2), (2, 5)],  # 0→1(权2)、0→2(权5)
        [(2, 1), (3, 4)],  # 1→2(权1)、1→3(权4)
        [(3, 2), (4, 6)],  # 2→3(权2)、2→4(权6)
        [(4, 3)],           # 3→4(权3)
        []                  # 4无出边
    ]
    source = 0  # 源点为路口0

    # 1. 初始静态最短路径计算
    dist, prev = init_static_dijkstra(initial_graph, source)
    # 结果:[0,2,3,5,8] → 0→1(2), 0→1→2(3), 0→1→2→3(5), 0→1→2→3→4(8)
    print("初始最短距离:", dist)

    # 2. 动态事件1:边1→3权从4→2(路况变好)
    print("
动态事件1:边 1->3 权减少到 2")
    updated_graph, updated_dist, updated_prev = handle_edge_decrease(initial_graph, dist.copy(), prev.copy(), 1, 3, 2)
    # 结果:[0,2,3,4,7] → 0→1→3(4)替代原0→1→2→3(5),0→1→3→4(7)替代原8
    print("更新后最短距离:", updated_dist)

    # 3. 动态事件2:边2→3权从2→5(路况变差)
    print("
动态事件2:边 2->3 权增加到 5")
    updated_graph2, updated_dist2, updated_prev2 = handle_edge_increase(updated_graph, updated_dist.copy(), updated_prev.copy(), 2, 3, 5)
    # 结果:[0,2,3,4,7] → 原最短路径0→1→3未经过2→3,因此距离不变
    print("更新后最短距离:", updated_dist2)

    # 4. 动态事件3:新增边0→4,权为7
    print("
动态事件3:新增边 0->4,权为 7")
    updated_graph3, updated_dist3, updated_prev3 = handle_edge_add(updated_graph2, updated_dist2.copy(), updated_prev2.copy(), 0, 4, 7)
    # 结果:[0,2,3,4,7] → 新增边距离7与原0→1→3→4的7相同,因此距离不变
    print("更新后最短距离:", updated_dist3)

代码局限性与优化建议(数学建模时需注意)

  1. 非负边权限制:仅支持非负边权(Dijkstra算法的固有局限),若需处理负边权需改用动态Bellman-Ford或其他算法
  2. 源点硬编码handle_edge_increaseheap = [(dist[0], 0)]默认源点为0,若源点可变需修改为函数参数
  3. 邻接列表效率 :当前用列表存储邻接边,修改边权时需遍历整个列表(O(m)时间),可优化为邻接字典 (如graph[u] = {v: w}),将修改时间降至O(1)
  4. 并行化潜力:动态更新部分可并行处理节点扩散,但需注意线程安全

数学建模应用场景

  • 交通流优化:实时更新道路拥堵情况,维护从起点到所有目的地的最短路径
  • 网络路由:动态调整链路带宽/延迟,维护源节点到所有子网的最短路径
  • 物流配送:实时更新路段成本(如过路费、油价),维护配送中心到所有客户的最优路径
  • 电网调度:动态调整输电线路的损耗,维护电源点到所有负荷点的最短传输路径

总结:这段代码通过增量更新+路径树回溯的核心逻辑,实现了高效的动态最短路径维护,是数学建模中处理实时路径优化问题的重要工具。

相关推荐
这张生成的图像能检测吗6 小时前
(论文速读)基于迁移学习的大型复杂结构冲击监测
人工智能·数学建模·迁移学习·故障诊断·结构健康监测·传感器应用·加权质心算法
您好啊数模君9 小时前
数学建模优秀论文算法-LSTM算法
数学建模·lstm·lstm神经网络
t1987512810 小时前
大规模MIMO系统中最优波束形成编码的解析
数学建模
柯西极限存在准则13 小时前
第二章 数学与工程基础
数学建模
咋吃都不胖lyh13 小时前
多臂老虎机算法(Multi-Armed Bandit, MAB)详解
数学建模
泰迪智能科技16 小时前
分享|高校数学建模实验室建设整体解决方案
数学建模
2301_7644413317 小时前
Python实现深海声弹射路径仿真
python·算法·数学建模
我也要当昏君2 天前
时间复杂度
算法·数学建模
您好啊数模君2 天前
数学建模优秀论文算法-广度优先搜索(BFS)
数学建模·广度优先搜索(bfs)