2021-1 加权有向图 最短路径 理论准备

定义

  • 最短路径:在一幅加权有向图中,从顶点s到顶点v的最短路径指的是所有s到t中路径权重的最小者
  • 最短路径树:包含起点s到所有可达顶点的最短路径,s为根节点,树中的每条路径都是一条最短路径。

最短路径的性质

  • 路径是定向的。 最短的路径必须遵循其边缘的方向。 权重不一定是距离。 几何直觉可能会有所帮助,但是边缘权重权重可能代表时间或成本。
  • 并非所有顶点都需要可达。 如果t不能从s到达,则根本没有路径,因此从s到t没有最短的路径。 负权重会带来并发症。
  • 假设边缘权重为正(或为零)。 最短路径通常很简单。 我们的算法忽略了形成循环的零权重边缘,因此它们找到的最短路径没有循环。
  • 最短路径不一定是唯一的。 从一个顶点到另一个顶点可能有多个权重最低的路径;我们满足于找到其中任何一个。 可能存在平行边缘和自环。
  • 假设不存在平行边,并使用符号v-> w来指代从v到w的边,但是我们的代码可以轻松地处理它们。

摘自《算法 第四版》

最短路径API

算法 第四版

数据结构:

  • 父亲数组 edgeTo[]edgeTo[v]sv的最短路径上的最后一条边。
  • 距离数组 distTo[v]sv的最短路径的长度
  • 约定:设起点为s,初始化edgeTo[s]为null,dsitTo[s]0,其他元素的值初始化为不可达【起点s不可达的距离设为浮点数最大值DBL_MAX】。

边的松弛

  • 什么是松弛?
    两颗钉子间拉着一条橡皮筋,紧绷绷的。现在,找一个更近的钉子,当把橡皮筋一端换到这里时,缓解了橡皮筋的压力。
    在这里插入图片描述
  • 代码描述
void relax(DirectedEdge &e){
	int v=e.from(),w=e.to();
	if(distTo[w]>distTo[v]+e.weight()){
		distTo[w]=distTo[v]+e.weight();//换到更近的钉子上,橡皮筋成功松弛
		edgeTo[w]=e;
	}
}
  • 图示说明
    左边松弛失败,右边松弛成功
    在这里插入图片描述

顶点的松弛

放松从一个给定顶点指出的所有边

注意,从任意distTo[v]【起点s到v的距离】为有限值的顶点vdistTo[]为无穷的顶点的边都是有效的。如果v被放松,那么这些有效边都会被加到edgeTo[]。显然某条从起点指出的边将会是第一条被加入到edgeTo[]中的边。

在这里插入图片描述
代码描述:

void relax(EdgeWeightedDigraph G,int v){
	for(DirectedEdge e: G.adj(v)){//检查v的所有邻接点
		int w=e.to();
		if(distTo[w]>distTo[v]+e.weight()){
			distTo[w]=distTo[v]+e.weight();
			edgeTo[w]=e;//起点指向w最短路径的最后一条边
		}
	}
}

命题P(最短路径的最优性条件)

G是一幅加权有向图,顶点sG中的起点,distTo[]是一个由顶点索引的数组,保存的是G中的路径长度。

对于从s可达的所有顶点vdistTo[v]的值是sv的某条路径的长度,对于从s不可达的所有顶点v,该值为无穷大。

当且仅当对于vw的任意一条边e,这些值都满足distTo[w] <= distTo[v]+e.weight()时(换句话说,不存在有效边时),他们是最短路径长度。

关于证明:
在这里插入图片描述

通用的最短路径算法

distTo[s]初始化为0,其他distTo[]初始化为无穷大,继续如下操作

放松G中的任意边,直到不存在有效边为止。

对于任意从sw的顶点,distTo[w]的值就是起点sw的最短路径的长度,且edgeTo[w]为该路径上的最后一条边。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最短路径可以使用Dijkstra算法或Bellman-Ford算法求解。 1. Dijkstra算法 Dijkstra算法适用于边权为非负数的图,可以求出单源最短路径。 具体步骤: - 初始化:将起点的距离设置为0,其他点的距离设置为无穷大。 - 选取最短距离的点:从未确定最短路径的点中选取距离最短的点作为当前点。 - 更新距离:遍历当前点的邻居节点,更新其距离(如果通过当前点到达邻居节点的距离比已知的距离更短)。 - 标记已确定的最短距离:将当前点标记为已确定最短距离。 - 重复上述步骤,直到所有点的最短路径都被确定。 2. Bellman-Ford算法 Bellman-Ford算法可以处理边权为任意实数的图,可以检测负权环。 具体步骤: - 初始化:将起点的距离设置为0,其他点的距离设置为无穷大。 - 松弛操作:遍历所有边,如果通过一条边到达目标节点的距离比已知的距离更短,更新目标节点的距离。 - 检测负权环:重复执行第2步,直到没有节点的距离再发生变化或者执行了节点数次松弛操作。如果还有节点的距离可以被更新,说明存在负权环。 边介数可以使用Brandes算法求解。 Brandes算法适用于有向图和无向图,可以计算任意节点对之间的边介数。 具体步骤: - 初始化:对于每个节点v,将其介数值设为0。 - 遍历所有节点:对于每个节点s,执行下列步骤: - 初始化:对于每个节点v,将其距离d设为无穷大,最短路径数sigma设为0,前驱节点列表P为空。 - 设置起点的距离为0,最短路径数为1。 - 使用Dijkstra算法计算出从起点s到所有其他节点的最短路径,并记录每个节点v的距离d和最短路径数sigma。 - 使用反向遍历,计算从s出发经过每个节点v的最短路径条数,并将其累加到v的介数值中。 - 标准化:对于每个节点v,将其介数值除以2。 参考代码: ``` def dijkstra(graph, start): dist = {v: float('inf') for v in graph} dist[start] = 0 visited = set() while len(visited) < len(graph): v = min((d, v) for (v, d) in dist.items() if v not in visited)[1] visited.add(v) for w, d in graph[v]: if dist[v] + d < dist[w]: dist[w] = dist[v] + d return dist def brandes(graph): b = {v: 0 for v in graph} for s in graph: dist = dijkstra(graph, s) sigma = {v: 0 for v in graph} sigma[s] = 1 P = {v: [] for v in graph} for t in sorted(graph, key=dist.get): for w, d in graph[t]: if dist[t] + d == dist[w]: sigma[w] += sigma[t] P[w].append(t) delta = {v: 0 for v in graph} while P: w = P.popitem()[0] for v in P[w]: delta[v] += sigma[v] / sigma[w] * (1 + delta[w]) if w != s: b[w] += delta[w] / 2 return b ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值