【问题标题】:Finding shortest path in a graph, with additional restrictions在图形中查找最短路径,有附加限制
【发布时间】:2016-02-04 04:55:15
【问题描述】:

我有一个带有 2n 个顶点的图,其中每条边都有定义的长度。好像是**

**.

我正在尝试找到从 uv 的最短路径的长度(边长的最小总和),另外还有 2 个限制:

  • 路径包含的蓝色边数与红色边数相同。
  • 路径包含的黑边数不大于p

我想出了一个我认为可行的指数时间算法。它以以下方式遍历所有长度为 n - 1 的二进制组合,这些组合表示从 u 开始的路径:

  • 0 是蓝边
  • 1 是红边
  • 只要有黑边
    • 组合以 1 开头。第一条边(来自 u)是左边的第一个黑色边。
    • 组合以 0 结尾。那么最后一个边(到v)就是右边最后一个黑色的。
    • 相邻的数字不同。这意味着我们从蓝色边缘变为红色边缘(反之亦然),所以中间有一个黑色边缘。

此算法将忽略不满足前面提到的 2 个要求的路径,并计算满足的路径的长度,然后找到最短的路径。但是这样做可能会非常慢,我正在寻找一些技巧来提出更快的算法。我怀疑可以通过动态编程来实现,但我真的不知道从哪里开始。任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 这听起来像是一道作业题。您能否向我们展示您自己解决此问题的尝试?
  • @Xirema 他知道。对于问题的后半部分。但现在是指数级的时间,他问是否有更好的方法
  • 我认为 n 是奇怪的。难道你不能从蓝边走到一半,跳到红边,然后在红边走剩下的路吗?

标签: c++ algorithm performance graph shortest-path


【解决方案1】:

我觉得Dynamic Programming 有问题。

In here, v,u are arbitrary nodes.
Source node: s
Target node: t

For a node v, such that its outgoing edges are (v,u1) [red/blue], (v,u2) [black].
D(v,i,k) = min { ((v,u1) is red ? D(u1,i+1,k) : D(u1,i-1,k)) + w(v,u1) ,
                  D(u2,i,k-1) + w(v,u2) }
D(t,0,k) = 0           k <= p
D(v,i,k) = infinity    k > p //note, for any v
D(t,i,k) = infinity    i != 0

解释:

  • v - 当前节点
  • i - #reds_traversed - #blues_traversed
  • k - #black_edges_left

停止子句在目标节点,到达时结束,只允许在 i=0 和 k 时到达

递归调用在每个点检查“哪个更好?通过黑色或通过红色/蓝色”,并从两个选项中选择最佳解决方案。

这个想法是,D(v,i,k) 是从v 到目标 (t) 的最佳结果,使用的#reds-#bluesi,您最多可以使用k 黑边。
由此我们可以得出D(s,0,p)是从源头到达目标的最优结果。

由于|i| &lt;= n, k&lt;=p&lt;=n - 算法的总运行时间为O(n^3),假设在动态规划中实现。

【讨论】:

  • 在大问题中,存储通常被认为比时间更“昂贵”。所以你最坏的情况下 N^3 时间可能很好,但你最好的情况下 N^3 存储不太好。在了解您的存储如何工作时,我是否遗漏了什么?我假设这个问题对存储的持续时间访问需要直接布局存储。所以你必须全部分配,不管你是否全部使用。如果有一种方法(我没有看到)只消耗真正使用的东西,但仍然在恒定时间内查找,那会好得多。
  • 如果您只关心最坏情况(而不是平均情况),那么我的版本(解释时间比您的版本要长得多)将只使用恒定倍数的更少时间和存储,而不是改进O(n^3).
  • @JSF 是的,这是经典 DP 的 O(n^3) 空间和时间,但可以优化到 O(n^2) 空间,因为您只依赖于 2 个先前处理的节点每次通过。 (类似于斐波那契的 DP,您只存储“当前”、“上一个”来计算“下一个”,而不是存储所有已经计算的值)
【解决方案2】:

编辑:不知何故,我查看了问题中的“寻找最短路径”短语,而忽略了原始问题后来澄清意图的“长度”短语。因此,我在下面的两个答案都存储了大量额外的数据,以便在计算出它的长度后轻松回溯正确的路径。如果您在计算长度后不需要回溯,我的原始版本可以将其第一个维度从 N 更改为 2,并且只存储一个奇数 J 和一个偶数 J,覆盖任何旧的。我更快的版本可以降低管理 J、R 交互的所有复杂性,并且只需将其外部级别存储为 [0..1][0..H] 这些都不会改变时间,但它会改变存储空间。

要理解我的答案,首先要了解一个粗略的 N^3 答案:(我不知道我的实际答案是否比粗略的 N^3 具有更好的最坏情况,但它具有更好的平均情况)。

注意N必须是奇数,表示为N=2H+1。 (P 也必须是奇数。如果给定偶数 P,则减少 P。但如果 N 是偶数,则拒绝输入。)

使用 3 个真实坐标和 1 个隐含坐标存储成本:
J = 列 0 到 N
R = 红色边数 0 到 H
B = 黑边计数 0 到 P
S = 奇数或偶数(S 只是 B%1

我们将计算/存储成本[J][R][B] 作为使用恰好 R 个红色边缘和恰好 B 个黑色边缘到达 J 列的最低成本方式。 (我们也使用了 J-R 蓝色边缘,但这是多余的)。
为方便起见,直接写入cost,但通过访问器c(j,r,b) 读取它,当r&lt;0 || b&lt;0 时返回BIG,否则返回cost[j][r][b]。

那么最里面的一步就是:

If (S)
   cost[J+1][R][B] = red[J]+min( c(J,R-1,B), c(J,R-1,B-1)+black[J] );
else
   cost[J+1][R][B] = blue[J]+min( c(J,R,B), c(J,R,B-1)+black[J] );

将 cost[0][0][0] 初始化为零,对于超粗版本,将所有其他 cost[0][R][B] 初始化为 BIG。
您可以超级粗略地循环增加 J 序列以及您喜欢计算所有这些的任何 R、B 序列。

最后,我们可以找到答案: min( min(cost[N][H][all odd]), black[N]+min(cost[N][H][all even]) )

但一半的 R 值并不是问题的一部分。在前半部分,任何R&gt;J 都是不可能的,而在后半部分,任何R&lt;J+H-N 都是无用的。您可以轻松避免计算这些。使用更智能的访问器函数,您可以避免在需要计算的边界情况下使用从未计算过的位置。

如果任何新成本[J][R][B] 不小于相同 J、R 和 S 的成本但低于 B,则该新成本是无用数据。如果结构的最后一个暗点是映射而不是数组,我们可以轻松地计算一个从存储空间和时间中删除无用数据的序列。但是,减少的时间会乘以这些地图的平均大小(最大为 P)的对数。所以在一般情况下可能会赢,但在最坏的情况下可能会输。

稍微考虑一下成本所需的数据类型和 BIG 所需的值。如果该数据类型中的某个精确值既与最长路径一样大,又小至可以存储在该数据类型中的最大值的一半,那么对于 BIG 来说这是一个微不足道的选择。否则,您需要更谨慎地选择以避免任何舍入或截断。


如果您遵循所有这些,您可能会理解我认为难以解释的更好方法之一:这将使元素大小增加一倍,但将元素数量减少到一半以下。它将获得 std::map 调整到基本设计的所有好处,而无需 log(P) 成本。它将在不影响病理病例时间的情况下缩短平均时间。

定义一个包含成本和黑色计数的结构 CB。主存储是vector&lt;vector&lt;CB&gt;&gt;。对于每个有效的 J,R 组合,外部向量都有一个位置。这些是常规模式,因​​此我们可以轻松计算给定 J,R 或给定位置的 J,R 在向量中的位置。但是以增量方式保留它们会更快,因此 J 和 R 是隐含的,而不是直接使用的。向量应保留为其最终大小,约为 N^2/4。最好预先计算H,0 的索引

每个内部向量都有严格递增的B序列中的C,B对,严格递减的C序列。一次生成一个内部向量(在临时向量中),然后复制到它们的最终位置,然后只读取(不修改)。在每个内部向量的生成中,候选 C,B 对将按递增的 B 序列生成。所以在构建临时向量时保持 bestOdd 和 bestEven 的位置。然后,只有当每个候选者的 C 低于最佳(或最佳尚不存在)时,才会将每个候选者推入向量中。我们还可以将所有 B&lt;P+J-N 视为 B==S,因此该范围内的较低 C 替换而不是推送。

外向量的隐含(从未存储)J,R 对以 (0,0) (1,0) (1,1) (2,0) 开始,以 (N-1,H-1) 结束) (N-1,H) (N,H)。以增量方式处理这些索引是最快的,所以当我们计算隐含位置 J,R 的向量时,我们将 V 作为 J,R 的实际位置,U 作为 J-1 的实际位置,R 和 minU 作为 J-1 的第一个位置,?和 minV 作为 J 的第一个位置,?和 minW 作为 J+1 的第一个位置,?
在外循环中,我们简单地将 minV 复制到 minU 并将 minW 复制到 minV 和 V,并且很容易计算新的 minW 并确定 U 是从 minU 还是 minU+1 开始。

内部的循环将 V 推进到(但不包括)minW,而每次推进 V 时推进 U,并且在典型位置使用位置 U-1 处的向量和位置 U 处的向量一起计算向量对于位置 V。但是您必须涵盖不使用 U-1 处的向量的 U==minU 的特殊情况和仅使用 U-1 处的向量的 U==minV 的特殊情况。

当组合两个向量时,您可以通过 B 值同步遍历它们,使用一个或另一个根据遇到的 B 值生成候选者(见上文)。

概念:假设您了解如何存储具有隐含 J,R 和显式 C,B 的值:其含义是存在一条以成本 C 使用恰好 R 个红色分支和恰好 B 个黑色分支的列 J 的路径 并且不存在到列 J 的路径,该路径使用恰好 R 个红色分支和相同的 S,其中 C'B' 中的一个更好,另一个不差。

【讨论】:

    【解决方案3】:

    您的指数算法本质上是一个深度优先搜索树,您可以在其中跟踪下降时的成本。

    您可以通过跟踪迄今为止看到的最佳解决方案并修剪任何超出目前最佳解决方案的分支来使其成为分支定界。

    或者,您可以将其设为广度优先搜索,按成本排序,这样一旦您找到任何解决方案,它就是最好的。

    我过去这样做的方式是深度优先,但需要预算。 我修剪任何超出预算的分支。 然后我运行预算为 0 的情况。 如果它没有找到任何解决方案,我会以预算 1 运行它。 我不断增加预算,直到找到解决方案。 这可能看起来像很多重复,但由于每次运行访问的节点都比前一次多得多,所以前一次运行并不重要。

    这是解决方案成本的指数,而不是网络规模。

    【讨论】:

      猜你喜欢
      • 2022-01-20
      • 1970-01-01
      • 1970-01-01
      • 2016-06-25
      • 1970-01-01
      • 2014-12-04
      • 2021-11-27
      • 2022-11-19
      • 2022-01-10
      相关资源
      最近更新 更多