在没有任何负前缀的图表中查找最短路径

时间:2012-03-01 02:02:20

标签: algorithm constraints shortest-path bellman-ford

  

在具有正边缘和负边缘的有向图中找到从源到目的地的最短路径,使得在路径中的任何点处,在其之前的边缘的总和为负。如果没有这样的路径报告也是如此。

我试图使用改良的Bellman Ford,但找不到正确的解决方案。


我想澄清几点:

  1. 是的,可以有负重量循环。
  2. n是边数。
  3. 假设问题有解决方案,则存在O(n)长度路径。
  4. + 1 / -1边缘权重。

9 个答案:

答案 0 :(得分:14)

不可否认,这不是一个建设性的答案,但是在评论中发帖太长了......

在我看来,这个问题包含二进制以及离散的背包问题,所以最坏情况下的运行时间最多只是pseudo-polynomial。考虑如下连接和加权的图表:

Graph with initial edge with weight x and then a choice of -a(i) or 0 at each step

然后等效的二进制背包问题试图从集{ a 0 ,..., a 中选择权重 n }最大化Σ a i 其中Σ a < sub> i &lt; X

作为旁注,如果我们引入加权循环,很容易构建无界背包问题。

因此,您可能选择的任何实用算法的运行时间取决于您认为的“平均”情况。对于我没有考虑过或者没有考虑过的问题是否存在限制?你似乎很确定这是一个O( n 3 )问题。 (虽然在这种情况下 n 是什么?)

答案 1 :(得分:11)

Peter de Rivaz在评论中指出此问题包含HAMILTONIAN PATH作为特例。他的解释有点简洁,我花了一些时间来弄清楚细节,所以我画了一些图表,以便其他可能正在挣扎的人受益。我已经发布了这个帖子社区维基。

我将使用以下带有六个顶点的图表作为示例。其中一条汉密尔顿路径以粗体显示。

Graph with six vertices and seven edges; one of its Hamiltonian paths shown in bold

给定一个带有 n 顶点的无向图,我们想要找到一个汉密尔顿路径,我们构造一个新的加权有向图,其中 n 2 顶点,加上START和END顶点。标记原始顶点 v i 和新顶点 w ik 表示0≤ i k &lt; 名词的。如果 v i v j <之间存在优势/ sub>在原始图中,然后对于0≤ k &lt; n -1新图表中的边缘从 w ik w j k +1),权重-2 j 和来自 w jk to w i k +1),权重-2 i 。从START到 w i0 的边缘,权重2 n - 2 < sup> i - 1和 w i n -1)< / sub>到END,权重为0。

最容易认为这种结构相当于以2 n - 1的分数开始,然后减去2 i 每次访问 w ij 时。 (这就是我如何绘制下图。)

从START到END的每条路径必须完全访问 n + 2个顶点(每行一个,加上START和END),因此沿路径求和的唯一方法是它只能访问每一列一次。

所以这是原始图形,其中六个顶点转换为具有38个顶点的新图形。原始哈密尔顿路径对应于以粗体绘制的路径。您可以验证路径上的总和为零。

Same graph converted to shortest-weighted path format as described.

答案 2 :(得分:5)

更新: OP现在有几轮澄清,现在是一个不同的问题。我将这里留下来记录我对问题的第一个版本的想法(或者更确切地说是我对它的理解)。我会为当前版本的问题尝试一个新的答案。 更新结束

遗憾的是,OP尚未澄清一些未解决的问题。我将假设以下内容:

  1. 重量为+/- 1。
  2. n是顶点数
  3. 第一个假设显然不会失去一般性,但它对n的值有很大的影响(通过第二个假设)。如果没有第一个假设,即使是一个微小的(固定的)图形也可以通过无限制地改变权重来获得任意长的解。

    我提出的算法非常简单,类似于众所周知的图算法。我不是图形专家,所以我可能会在某些地方用错字。随意纠正我。

    1. 对于源顶点,请记住cost 0.将(source,0)添加到待办事项列表。
    2. 从待办事项列表中弹出一个项目。跟随顶点的所有外围边缘,计算新成本c以到达新顶点v。如果新成本有效(c> = 0 且c <= n ^ 2 ,请参见下文)并且不记得v,添加到v的记忆成本值,并将(v,c)添加到你的待办事项列表。
    3. 如果待办事项列表不为空,请继续执行步骤2.(如果可以使用成本0到达目的地,则提前中断。)
    4. 很明显,不是直接死胡同的每个“步骤”都会创建一个新的(顶点,成本)组合。这些组合最多将存储n * n ^ 2 = n ^ 3,因此,在某种意义上,该算法为O(n ^ 3)。

      现在,为什么这会找到最佳路径?我没有真正的证据,但我认为以下几个想法证明了为什么我认为这就足够了,而且它们可能是有可能的可以变成一个真实的证明。

      我认为很明显,我们唯一需要表明的是条件c <= n ^ 2就足够了。

      首先,让我们注意到任何(可到达的)顶点都可以以低于n的成本到达。

      让(v,c)成为最佳路径的一部分并且c> n ^ 2。 如c> n,在到达(v,c)之前必须在路径上有一些循环,其中循环的成本是0 0。 m2&gt; -n。

      此外,让v可以从来源获得,费用为0&lt; = c1&lt; n,通过接触上述第一周期的路径,并且使目的地可以从v到达,其中成本0&lt; = c2&lt; n,通过一条接触上述另一个循环的路径。

      然后我们可以构建从源到v的路径,成本为c1,c1 + m1,c1 + 2 * m1,...,以及从v到目的地的路径,成本为c2,c2 + m2,c2 + 2 * m2, ......选择0&lt; = a&lt; = m2并且0&lt; = b&lt; = m1使得c1 + c2 + a * m1 + b * m2最小并且因此是最佳路径的成本。在该最佳路径上,v将具有成本c1 + a * m1 < n ^ 2.

      如果m1和m2的gcd是1,则成本将为0.如果gcd是> 1,那么有可能选择其他周期,使得gcd变为1.如果不可能,那么最优解也是不可能的,并且最优解将会有正成本。

      (是的,我可以看到这种证明尝试的几个问题。可能有必要采取几个正或负周期成本的gcd等。但我会对一个反例非常感兴趣。)

      这是一些(Python)代码:

      def f(vertices, edges, source, dest):
          # vertices: unique hashable objects
          # edges: mapping (u, v) -> cost; u, v in vertices, cost in {-1, 1}
      
          #vertex_costs stores the possible costs for each vertex
          vertex_costs = dict((v, set()) for v in vertices)
          vertex_costs[source].add(0) # source can be reached with cost 0
      
          #vertex_costs_from stores for each the possible costs for each vertex the previous vertex
          vertex_costs_from = dict()
      
          # vertex_gotos is a convenience structure mapping a vertex to all ends of outgoing edges and their cost
          vertex_gotos = dict((v, []) for v in vertices)
          for (u, v), c in edges.items():
              vertex_gotos[u].append((v, c))
      
          max_c = len(vertices) ** 2 # the crucial number: maximal cost that's possible for an optimal path
      
          todo = [(source, 0)] # which vertices to look at
      
          while todo:
              u, c0 = todo.pop(0)
              for v, c1 in vertex_gotos[u]:
                  c = c0 + c1
                  if 0 <= c <= max_c and c not in vertex_costs[v]:
                      vertex_costs[v].add(c)
                      vertex_costs_from[v, c] = u
                      todo.append((v, c))
      
          if not vertex_costs[dest]: # destination not reachable
              return None # or raise some Exception
      
          cost = min(vertex_costs[dest])
      
          path = [(dest, cost)] # build in reverse order
          v, c = dest, cost
          while (v, c) != (source, 0):
              u = vertex_costs_from[v, c]
              c -= edges[u, v]
              v = u
              path.append((v, c))
      
          return path[::-1] # return the reversed path
      

      一些图表的输出(边缘及其重量/路径/成本在路径的每个点;对不起,没有漂亮的图像):

      AB+ BC+ CD+ DA+             AX+ XY+ YH+ HI- IJ- JK- KL- LM- MH-
       A  B  C  D  A  X  Y  H  I  J  K  L  M  H
       0  1  2  3  4  5  6  7  6  5  4  3  2  1
      AB+ BC+ CD+ DE+ EF+ FG+ GA+ AX+ XY+ YH+ HI- IJ- JK- KL- LM- MH-
       A  B  C  D  E  F  G  A  B  C  D  E  F  G  A  B  C  D  E  F  G  A  X  Y  H  I  J  K  L  M  H  I  J  K  L  M  H  I  J  K  L  M  H  I  J  K  L  M  H
       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
      AB+ BC+ CD+ DE+ EF+ FG+ GA+ AX+ XY+ YH+ HI- IJ- JK- KL- LM- MN- NH-
       A  X  Y  H
       0  1  2  3
      AB+ BC+ CD+ DE+ EF+ FG+ GA+ AX+ XY+ YH+ HI- IJ- JK- KL- LM- MN- NO- OP- PH-
       A  B  C  D  E  F  G  A  B  C  D  E  F  G  A  B  C  D  E  F  G  A  B  C  D  E  F  G  A  B  C  D  E  F  G  A  B  C  D  E  F  G  A  X  Y  H  I  J  K  L  M  N  O  P  H  I  J  K  L  M  N  O  P  H  I  J  K  L  M  N  O  P  H  I  J  K  L  M  N  O  P  H  I  J  K  L  M  N  O  P  H
       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
      

      以下是生成该输出的代码:

      def find_path(edges, source, path):
          from itertools import chain
      
          print(edges)
          edges = dict(((u, v), 1 if c == "+" else -1) for u, v, c in edges.split())
          vertices = set(chain(*edges))
      
          path = f(vertices, edges, source, dest)
          path_v, path_c = zip(*path)
          print(" ".join("%2s" % v for v in path_v))
          print(" ".join("%2d" % c for c in path_c))
      
      source, dest = "AH"
      
      edges = "AB+ BC+ CD+ DA+             AX+ XY+ YH+ HI- IJ- JK- KL- LM- MH-"
      # uv+ means edge from u to v exists and has cost 1, uv- = cost -1
      find_path(edges, source, path)
      
      edges = "AB+ BC+ CD+ DE+ EF+ FG+ GA+ AX+ XY+ YH+ HI- IJ- JK- KL- LM- MH-"
      find_path(edges, source, path)
      
      edges = "AB+ BC+ CD+ DE+ EF+ FG+ GA+ AX+ XY+ YH+ HI- IJ- JK- KL- LM- MN- NH-"
      find_path(edges, source, path)
      
      edges = "AB+ BC+ CD+ DE+ EF+ FG+ GA+ AX+ XY+ YH+ HI- IJ- JK- KL- LM- MN- NO- OP- PH-"
      find_path(edges, source, path)
      

答案 3 :(得分:2)

正如Kaganar指出的那样,我们基本上必须做出一些假设才能得到一个多时间算法。假设边长在{-1,1}中。给定图,构造一个加权的无上下文语法,识别从源到目的地的有效路径,权重等于超出1个边的数量(它概括了平衡括号的语法)。通过将所有内容初始化为无穷大或1,根据是否存在其RHS没有非终结符的生产,然后放松n-1次,其中n是非终结符的数量,为每个非终端计算最便宜生产的成本。 / p>

答案 4 :(得分:0)

我会在这里使用递归暴力强制: 像(伪代码,以确保它不是语言特定的)

你需要:

  • 显示你可以在哪里和你不能去的地方的二维数组,这不应该包括“禁止值”,比如在负边缘之前,你可以选择添加一个垂直和水平的“平移”以确保它开始在[0] [0]
  • 包含最短路径的整数(静态)
  • 一个2个插槽的数组,显示目标。 [0] = x,[1] = y

你会这样做:

function(int xPosition, int yPosition, int steps)
{
if(You are at target AND steps < Shortest Path)
    Shortest Path = steps
if(This Position is NOT legal)
    /*exit function*/
else
    /*try to move in every legal DIRECTION, not caring whether the result is legal or not
    but always adding 1 to steps, like using:*/
    function(xPosition+1, yPosition, steps+1);
    function(xPosition-1, yPosition, steps+1);
    function(xPosition, yPosition+1, steps+1);
    function(xPosition, yPosition-1, steps+1);
}

然后运行它 function(StartingX,StartingY,0);

最短路径将包含在静态外部int

答案 5 :(得分:0)

我想澄清几点:

  1. 是的,可以有负重量循环。
  2. n是边数。
  3. 权重不仅仅是+ 1 / -1。
  4. 假设问题有解决方案,则存在O(n)长度路径。 (n是边数)

答案 6 :(得分:0)

虽然人们已经证明不存在快速解决方案(除非P = NP)..

我认为对于大多数图表(95%以上),您应该能够很快找到解决方案。

我利用这样一个事实:如果有周期,那么通常有很多解决方案,我们只需要找到其中一个。我的想法可能有一些明显的漏洞,所以请告诉我。

Ideas:

<强> 1。找到最接近目的地的负循环。表示周期和目的地之间的最短距离为d(结束,negC)

(我认为这是可能的,一种方法可能是使用floyds以负循环检测(i,j),然后从目标开始广泛搜索,直到你遇到连接到负循环的东西。)

<强> 2。找到最接近起始节点的正循环,表示从起点开始的距离为d(start,posC)

(我认为95%的图表可以轻松找到这些周期)

    Now we have cases:
a) there is both the positive and negative cycles were found:
The answer is d(end,negC).

b) no cycles were found:
simply use shortest path algorithm?

c) Only one of the cycles was found. We note in both these cases the problem is the same due to symmetry (e.g. if we swap the weights and start/end we get the same problem). I'll just consider the case that there was a positive cycle found.

find the shortest path from start to end without going around the positive cycle. (perhaps using modified breadth first search or something). If no such path exists (without going positive).. then .. it gets a bit tricky.. we have to do laps of the positive cycle (and perhaps some percentage of a lap).
If you just want an approximate answer, work out shortest path from positive cycle to end node which should usually be some negative number. Calculate number of laps required to overcome this negative answer + the distance from the entry point to the cycle to the exit point of the cycle. Now to do better perhaps there was another node in the cycle you should have exited the cycle from... To do this you would need to calculate the smallest negative distance of every node in the cycle to the end node.. and then it sort of turns into a group theory/ random number generator type problem... do as many laps of the cycle as you want till you get just above one of these numbers.
祝你好运,希望我的解决方案适用于大多数情况。

答案 7 :(得分:0)

目前的假设是:

  1. 是可以有负重量循环。
  2. n是边数。
  3. 假设问题有解决方案,则存在O(n)长度路径。
  4. + 1 / -1边缘权重。
  5. 我们可以假设顶点的数量最多为n而不失一般性。 递归地遍历图形并记住每个顶点的成本值。如果已经记住了顶点的成本,或者成本是否定的,则停止。

    在O(n)步骤之后,未到达目的地并且没有解决方案。 否则,对于每个O(n)顶点,我们记住最多O(n)个不同的成本值,并且对于这些O(n ^ 2)组合中的每一个,可能有多达n次不成功的尝试步行到其他顶点。总而言之,它是O(n ^ 3)。 q.e.d。

    更新:当然,还有一些可疑的东西。假设3意味着什么:如果问题有解决方案,则存在O(n)长度路径?任何解决方案都必须检测到这一点,因为它还必须报告是否没有解决方案。但是,检测到这一点是不可能的,因为这不是算法处理的单个图的属性(它是渐近行为)。

    (同样清楚的是,并非所有可以到达目的地的图都有一个长度为O(n)的解决方案路径:取一条m边的权重-1,在此之前有一个简单的m边循环和总重量+1)。

    [我现在意识到我的其他答案中的大部分Python代码(尝试第一个版本的问题)都可以重复使用。]

答案 8 :(得分:0)

步骤1:请注意您的答案最多为2 * n(如果存在) 第2步:创建一个顶点为[vertex] [cost]对的新图形。 (2 * n ^ 2个顶点)
第3步:请注意,新图形的所有边都等于1,每个[顶点] [成本]对最多2 * n。
步骤4:从[start] [0]
开始,在此图表上执行dfs 步骤5:找到最小k,这样[finish] [k]是可访问的。

总复杂度最多为O(n ^ 2)* O(n)= O(n ^ 3)

编辑:第1步澄清。
如果有一个正循环,从开始可以访问,你可以一直到n。现在你可以走到任何可访问的顶点,不超过n个边,每个都是+1或-1,留下[0; 2n]范围。 否则你将走过负循环,或者不超过n + 1,它们不是负循环,只留下[0; n]范围。