查找具有最大最小权重的路径

7
我正在尝试设计一种算法,以寻找有向图上的路径。这不是传统的路径,我找不到已经完成此任务的任何参考资料。
我希望找到最小权重最大的路径。
例如,如果有两条路径,它们的权重分别为10->1->10和2->2->2,则第二条路径被认为比第一条更好,因为最小权重(2)大于第一条路径的最小权重(1)。
如果有人能够想出一种方法来实现这一点,或者只是指引我一些参考资料,那将非常有用 :)
编辑:似乎我忘记提到我正在尝试从一个特定的顶点到达另一个特定的顶点。这是非常重要的一点 :/
编辑2:正如下面一位用户指出的那样,边缘权重是非负的。

那么是最大值最小还是最小值最大?请编辑示例并进行更正。 - Jasiu
@Jasiu:标题和示例都说“最大最小权重”(即最小权重尽可能大的路径)。有什么需要更正的吗? - ShreevatsaR
我想确认一下,您只对所有路径中具有最小边数的路径感兴趣,是吗?也就是说,如果存在一个权重为(2, 2, 2)的路径,您不会对路径(5, 6, 7, 8)感兴趣,对吗? - j_random_hacker
@j_random_hacker:不,(2,2,2)的最小权重为2,而(5,6,7,8)的最小权重为5,因此后者更好。 - ShreevatsaR
7个回答

8

我正在复制this的答案,并添加算法正确性的证明:

我会使用Dijkstra的某种变体。我直接从维基百科上获取了下面的伪代码,只改变了5个小地方:

  1. dist重命名为width(从第3行开始)
  2. 将每个width初始化为-无穷大(第3行)
  3. 将源的宽度初始化为无穷大(第8行)
  4. 将完成标准设置为-无穷大(第14行)
  5. 修改更新函数和符号(第20 + 21行)

1  function Dijkstra(Graph, source):
2      for each vertex v in Graph:                                 // Initializations
3          width[v] := -infinity  ;                                // Unknown width function from 
4                                                                  // source to v
5          previous[v] := undefined ;                              // Previous node in optimal path
6      end for                                                     // from source
7      
8      width[source] := infinity ;                                 // Width from source to source
9      Q := the set of all nodes in Graph ;                        // All nodes in the graph are
10                                                                 // unoptimized – thus are in Q
11      while Q is not empty:                                      // The main loop
12          u := vertex in Q with largest width in width[] ;       // Source node in first case
13          remove u from Q ;
14          if width[u] = -infinity:
15              break ;                                            // all remaining vertices are
16          end if                                                 // inaccessible from source
17          
18          for each neighbor v of u:                              // where v has not yet been 
19                                                                 // removed from Q.
20              alt := max(width[v], min(width[u], width_between(u, v))) ;
21              if alt > width[v]:                                 // Relax (u,v,a)
22                  width[v] := alt ;
23                  previous[v] := u ;
24                  decrease-key v in Q;                           // Reorder v in the Queue
25              end if
26          end for
27      end while
28      return width;
29  endfunction

一些(手势)解释为什么这个算法有效:你从源点开始。从那里起,你有无限的能力来到达自己。现在你检查源点的所有邻居。假设边的容量不都相同(在你的例子中,假设 (s, a) = 300),那么没有比通过 (s, b) 更好地到达 b 的方法,因此你知道了 b 的最佳情况容量。你继续前往已知顶点集的最佳邻居,直到到达所有顶点。
算法的正确性证明:
在算法的任何时刻,将存在2组顶点A和B。集合A中的顶点是找到了正确的最大最小容量路径的顶点。集合B中的顶点是我们尚未找到答案的顶点。
归纳假设:在任何步骤中,集合A中的所有顶点都具有正确的最大最小容量路径值。也就是说,所有以前的迭代都是正确的。
基本情况的正确性:当集合A只有一个顶点S时。那么对于S的值是无穷大,这是正确的。
在当前迭代中,我们设置

val[W] = max(val[W], min(val[V], width_between(V-W)))

归纳步骤:假设W是集合B中具有最大val[W]的顶点。 W从队列中出列,并设置为答案val[W]。

现在,我们需要证明每条S-W路径的宽度都小于或等于val[W]。这总是正确的,因为到达W的所有其他方式都会通过集合B中的其他顶点(称其为X)。

对于集合B中的所有其他顶点X,val[X] ≤ val[W]

因此,到达W的任何其他路径都将受到val[X]的限制,val[X]永远不会大于val[W]。

因此,val[W]的当前估计值是最优的,因此算法计算出所有顶点的正确值。


第24行的意思是“在Q中减小v的键值”。 - An Yan
如果在Q中有两个相同的最大宽度,我们会选择确定的还是任意一个? - An Yan

4
你还可以使用“二分查找答案”的范例。也就是说,在权重上进行二分查找,对于每个权重w,测试是否可以在图中仅使用大于w的边找到路径。
通过二分查找找到的最大w值即为答案。请注意,您只需要检查路径是否存在,因此只需进行O(|E|)广度优先/深度优先搜索,而不是最短路径。因此,总复杂度为O(|E| * log(max W)),与Dijkstra / Kruskal / Prim的O(|E|log |V|)相当(我也无法立即看到这些的证明)。

1
这是一个有趣的技巧,听起来肯定能够奏效。 - Martin
@ShreevatsaR,你的二分查找答案的方法可以优化为线性时间。请参见此处http://en.wikipedia.org/wiki/Widest_path_problem#Undirected_graphs我在这里添加了算法正确性的证明https://dev59.com/NGMl5IYBdhLWcg3wP1HE#22890192 - Nikunj Banka
1
@NikunjBanka:我理解了你提供的维基百科页面上提到的线性时间算法。它依赖于线性时间中位数查找算法。(1)将所有|E|条边的权重找出中位数(称为M),用线性(O(|E|))时间完成。(2)如上所述,仅使用权重大于此中位数M的边,在图中检查可达性。如果找到了一条路径,则(3a)我们现在可以确定我们不需要任何权重小于M的边。删除它们并返回步骤1。否则,(3b) 合并 权重大于M的边(u,v)(将(u,v)折叠成单个顶点),然后返回步骤1。 - ShreevatsaR
1
@NikunjBanka:当您收缩边缘(u,v)时,对于原始图中与u或v相邻的每个其他顶点w,您都会向新顶点添加一条从w到新顶点的边。此边的权重为:如果w仅与两者之一相交,则为该边的权重,否则为两个权重中较大的一个。有关详细信息,请参见http://www.zib.de/Publications/Reports/ZR-06-22.pdf。 - ShreevatsaR
你解释的边缩减比我预想的要复杂。另外,你介意看一下这个问题吗 https://dev59.com/x37aa4cB1Zd3GeqPpmU9 。它非常相似,我认为它也应该通过二分查找解决答案。 - Nikunj Banka
显示剩余2条评论

3
使用Prim'sKruskal's算法,只需修改它们,使它们在发现您询问的顶点已连接时停止。
编辑:您要求最大最小值,但您的示例看起来像是您想要最小最大值。在最大最小值的情况下,Kruskal's算法将无法工作。
编辑:该示例是正确的,是我的错误。那么只有Prim's算法能够工作。

我一定要最大最小值。就像我的例子所示:(10,1,10)的最小值= 1 (2,2,2)的最小值= 2 最大值= 2 - Martin
是的,我一定读错了什么。Prim算法是留在游戏中的算法。 - Jasiu
1
Kruskal算法也可以使用。(按照权重降序排列边,逐个添加直到两个顶点在同一个分量中。) - ShreevatsaR
1
我并不确定 Prim 或 Kruskal 算法是否适用于这里。有没有人能够更新答案,提供更详细的证明呢?谢谢! - j_random_hacker
@j_random_hacker 我已经在这里添加了算法正确性的证明 https://dev59.com/NGMl5IYBdhLWcg3wP1HE#22890192 - Nikunj Banka
显示剩余4条评论

2

我不确定Prim算法在这里是否适用。看看这个反例:

V = {1, 2, 3, 4}

E = {(1, 2), (2, 3), (1, 4), (4, 2)}

weight function w:
  w((1,2)) = .1, 
  w((2,3)) = .3
  w((1,4)) = .2
  w((4,2)) = .25

如果您使用 Prim 算法来找到从 1 到 3 的最大最小路径,从 1 开始会选择 1 --> 2 --> 3 这条路径,而经过 4 的路径将获得最大最小距离。


1

这个问题可以用BFS算法解决,不过你需要两个变种:

  • 与其将每个节点标记为“已访问”,你需要将其标记为到达它的路径上的最小权值。

例如,如果I和J是相邻点,I具有值w1,它们之间的边的权重为w2,则J=min(w1, w2)。

  • 如果你到达一个值为w1的已标记节点,如果分配一个新值w2(并且w2>w1),则可能需要重新标记并再次处理它。这是必要的,以确保您获得所有最小值中的最大值。

例如,如果I和J是相邻点,I具有值w1,J具有值w2,它们之间的边的权重为w3,则如果min(w2, w3)>w1,则必须重新标记J并再次处理其所有邻居节点。


1
为什么这个程序会在多项式时间内终止?(看起来没问题,但不像是多项式时间。) - ShreevatsaR
1
我想象中该算法的最坏情况可能非常糟糕。 我认为该算法没有问题,除非“再次处理所有邻居”有点模糊,难道你不必要重新处理整个回到源路径吗? - Martin

0

好的,回答自己的问题只是为了尝试一下我在发布这个问题之前制定的可能解决方案所得到的反馈:

每个节点存储一个“路径片段”,这是到目前为止它自己的整个路径。

0)将当前顶点设置为起始顶点
1)从该顶点生成所有路径片段,并将它们添加到优先队列中
2)从优先级队列的顶部取出片段,将当前顶点设置为该路径的终止顶点
3)如果当前顶点是目标顶点,则返回路径
4)转到步骤1

我不确定这是否会找到最佳路径,我认为第三步中的退出条件有点过于雄心壮志。但我想不到更好的退出条件,因为此算法不关闭顶点(顶点可以在任意数量的路径片段中引用),您不能等待所有顶点关闭(例如Dijkstra算法)


干得好...这只是Jasiu建议的Prim算法。 ;) - Neil G
太棒了,我喜欢重新发明轮子的感觉 >_<好吧,我想我对他的建议有相当好的理解了 ;) - Martin

-1

你仍然可以使用Dijkstra算法!

不要使用+,而是使用min()运算符。
此外,您需要将堆/优先队列定向,使最大的元素位于顶部。

类似这样的代码应该可以工作:(我可能错过了一些实现细节)

let pq = priority queue of <node, minimum edge>, sorted by min. edge descending
push (start, infinity) on queue
mark start as visited
while !queue.empty:
   current = pq.top()
   pq.pop()
   for all neighbors of current.node:
      if neighbor has not been visited
          pq.decrease_key(neighbor, min(current.weight, edge.weight))

保证每当您到达一个节点时,您都会遵循最优路径(因为您按递减顺序找到所有可能性,并且通过添加边缘永远无法改进路径)

时间限制与Dijkstra的相同-O(Vlog(E))。

编辑:哦等等,这基本上就是您发布的内容。哈哈。


不,Dijkstra算法在这里不可行,原因类似于它不能用于带有负权边的图形。 - Christoph
(举个反例,看看我的发帖) - Christoph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接