【问题标题】:Is this Sedgewick code correct?这个 Sedgewick 代码正确吗?
【发布时间】:2014-12-01 01:33:37
【问题描述】:

我正在解决一个优化问题,其中我必须最大化流网络。我实现了一个基于 C++ 代码的流最大化算法,该算法基于以下 java 代码,该算法出现在 Sedgewick “Algorithms in Java, Third Edition, Part 5: Graph Algorithms”一书中,它使网络流最大化使用基于顶点的 PREFLOW-push 算法:

class NetworkMaxFlow
{ private Network G; private int s, t;
  private int[] h, wt;
  private void initheights()
  NetworkMaxFlow(Network G, int s, int t)
  { this.G = G; this.s = s; this.t = t;
    wt = new int[G.V()]; h = new int[G.V()];
    initheights();
    intGQ gQ = new intGQ(G.V());
    gQ.put(s); wt[t] = -(wt[s] = Edge.M*G.V());
    while (!gQ.empty())
    { int v = gQ.get();
      AdjList A = G.getAdjList(v);
      for (Edge e = A.beg(); !A.end(); e = A.nxt())
        { int w = e.other(v), cap = e.capRto(w);
          int P = cap < wt[v] ? cap : wt[v];
          if (P > 0 && v == s || h[v] == h[w]+1) // first observation (see below)
            { e.addflowRto(w, P);
              wt[v] -= P; wt[w] += P;
              if ((w != s) && (w != t)) gQ.put(w); // enqueue w if it is not source or sink
            }
        }
      if (v != s && v != t && wt[v] > 0) // why check v != t if t never enter the queue?
        { h[v]++; gQ.put(v); }
    }
  }
}

我的实现,基于该代码,未能最大化以下网络 执行后得到的流程如下 有了这个流,流值是8,但是最大值是9,如下图的流所示 根据我的理解,算法和书上的解释是一致的。但是,我看到了两件奇怪的事情

  1. 源中没有明确的预流阶段。它包含在while 中,并在谓词P &gt; 0 &amp;&amp; v == s 为真时首先执行一次。也许这样做是为了缩短代码
  2. 根据我的理解和书中的论述,sink 永远不会进入队列。但是,当高度增加时,代码会检查 v != t。有什么原因吗?

这是我在 C++ 中实现该算法的摘录

template <class Net, class Q_Type> typename Net::Flow_Type
generic_preflow_vertex_push_maximum_flow(Net & net)
{
  init_height_in_nodes(net); // breadth first traverse from sink to
                 // source. Nodes are labeled with their
                 // minimal distance (in nodes) to sink
  auto source = net.get_source();
  auto sink   = net.get_sink();

  using Itor = __Net_Iterator<Net>;
  Q_Type q; // generic queue (can be fifo, heap or random) of active nodes

  // preflow: floods all nodes connected to the source 
  for (Itor it(source); it.has_curr(); it.next()) 
    {
      auto arc  = it.get_curr();  
      arc->flow = arc->cap; // saturate arc to its maximum 
      auto tgt = net.get_tgt_node(arc);
      put_in_active_queue(q, tgt);
      assert(node_height<Net>(source) == node_height<Net>(tgt) + 1);
      assert(not is_residual<Net>(source, arc));
    }

  while (not q.is_empty()) // while there are active nodes
    {
      auto src = get_from_active_queue(q);
      auto excess = net.get_in_flow(src) - net.get_out_flow(src);

      for (Itor it(src); it.has_curr(); it.next()) 
        {
          auto arc = it.get_curr();
          auto tgt = net.get_connected_node(arc, src);

          if (node_height<Net>(src) != node_height<Net>(tgt) + 1)
            continue; // this arc is not eligible

          typename Net::Flow_Type flow_to_push;
          if (is_residual<Net>(src, arc))
            {
              flow_to_push = std::min(arc->flow, excess);
              arc->flow -= flow_to_push;
            }
          else
            {
              flow_to_push = std::min(arc->cap - arc->flow, excess);
              arc->flow += flow_to_push;
            }

          excess -= flow_to_push;
          if (tgt != sink and tgt != source)
            put_in_active_queue(q, tgt);
        }

    if (excess > 0) // src still active?
      { 
        node_height<Net>(src)++;
        put_in_active_queue(q, src);
      }
  }

  return net.flow_value(); // sum of all outing flow from source
}

¿有人发现我的代码和 Sedgewick 的代码之间存在逻辑上的不一致?我的印象是我的代码(也许还有 Sedgewick)没有正确处理高度的增加。但我不明白为什么

我展示了一个无法最大化的网络的详细执行跟踪(跟踪从 while 的第一个 q.get() 开始。括号中的值是高度的值。IN 是到节点的传入流. OUT 即将发布的。

例如,这条线

    4104 (2) --> 0 (1) pushing 1 from 4104 toward 0

指符合条件的弧 4104-->0。节点4104的高度为2,节点0的高度为1。表述“推1”意味着1个单位的流被推向目标节点(0)。 ================ 行分隔每个队列提取。队列是先进先出的,它的状态会在每次处理结束时打印出来。

请注意,多次推送或减少零流量单元,但目标节点变为活动状态。

这是执行跟踪

Initial Queue = 4104 4105 4106 4107 4108

Active node 4104 Height = 2 IN = 1 OUT = 0
    4104 (2) --> source (3) not eligible
    4104 (2) --> 0 (1) pushing 1 from 4104 toward 0
    4104 (2) --> 1 (1) pushing 0 from 4104 toward 1
    4104 (2) --> 2 (1) pushing 0 from 4104 toward 2
    4104 (2) --> 4 (1) pushing 0 from 4104 toward 4
    Excess = 0
    Queue = 4105 4106 4107 4108 0 1 2 4
================
Active node 4105 Height = 2 IN = 3 OUT = 0
    4105 (2) --> source (3) not eligible
    4105 (2) --> 1 (1) pushing 1 from 4105 toward 1
    4105 (2) --> 4 (1) pushing 1 from 4105 toward 4
    4105 (2) --> 6 (1) pushing 1 from 4105 toward 6
    Excess = 0
    Queue = 4106 4107 4108 0 1 2 4 6
================
Active node 4106 Height = 2 IN = 1 OUT = 0
    4106 (2) --> source (3) not eligible
    4106 (2) --> 1 (1) pushing 1 from 4106 toward 1
    4106 (2) --> 5 (1) pushing 0 from 4106 toward 5
    Excess = 0
    Queue = 4107 4108 0 1 2 4 6 5
================
Active node 4107 Height = 2 IN = 1 OUT = 0
    4107 (2) --> source (3) not eligible
    4107 (2) --> 1 (1) pushing 1 from 4107 toward 1
    4107 (2) --> 2 (1) pushing 0 from 4107 toward 2
    4107 (2) --> 3 (1) pushing 0 from 4107 toward 3
    4107 (2) --> 4 (1) pushing 0 from 4107 toward 4
    4107 (2) --> 6 (1) pushing 0 from 4107 toward 6
    Excess = 0
    Queue = 4108 0 1 2 4 6 5 3
================
Active node 4108 Height = 2 IN = 3 OUT = 0
    4108 (2) --> source (3) not eligible
    4108 (2) --> 1 (1) pushing 1 from 4108 toward 1
    4108 (2) --> 2 (1) pushing 1 from 4108 toward 2
    4108 (2) --> 4 (1) pushing 1 from 4108 toward 4
    4108 (2) --> 5 (1) pushing 0 from 4108 toward 5
    4108 (2) --> 6 (1) pushing 0 from 4108 toward 6
    Excess = 0
    Queue = 0 1 2 4 6 5 3
================
Active node 0 Height = 1 IN = 1 OUT = 0
    0 (1) --> sink (0) pushing 1 from 0 toward sink
    0 (1) --> 4104 (2) not eligible
    Excess = 0
    Queue = 1 2 4 6 5 3
================
Active node 1 Height = 1 IN = 4 OUT = 0
    1 (1) --> sink (0) pushing 2 from 1 toward sink
    1 (1) --> 4105 (2) not eligible
    1 (1) --> 4106 (2) not eligible
    1 (1) --> 4107 (2) not eligible
    1 (1) --> 4108 (2) not eligible
    Excess = 2    1 goes back onto queue with label 2
    Queue = 2 4 6 5 3 1
================
Active node 2 Height = 1 IN = 1 OUT = 0
    2 (1) --> sink (0) pushing 1 from 2 toward sink
    2 (1) --> 4108 (2) not eligible
    Excess = 0
    Queue = 4 6 5 3 1
================
Active node 4 Height = 1 IN = 2 OUT = 0
    4 (1) --> sink (0) pushing 2 from 4 toward sink
    4 (1) --> 4105 (2) not eligible
    4 (1) --> 4108 (2) not eligible
    Excess = 0
    Queue = 6 5 3 1
================
Active node 6 Height = 1 IN = 1 OUT = 0
    6 (1) --> sink (0) pushing 1 from 6 toward sink
    6 (1) --> 4105 (2) not eligible
    Excess = 0
    Queue = 5 3 1
================
Active node 5 Height = 1 IN = 0 OUT = 0
    5 (1) --> sink (0) pushing 0 from 5 toward sink
    Excess = 0
    Queue = 3 1
================
Active node 3 Height = 1 IN = 0 OUT = 0
    3 (1) --> sink (0) pushing 0 from 3 toward sink
    Excess = 0
    Queue = 1
================
Active node 1 Height = 2 IN = 4 OUT = 2
    1 (2) --> 4105 (2) not eligible
    1 (2) --> 4106 (2) not eligible
    1 (2) --> 4107 (2) not eligible
    1 (2) --> 4108 (2) not eligible
    Excess = 2    1 goes back onto queue with label 3
    Queue = 1
================
Active node 1 Height = 3 IN = 4 OUT = 2
    1 (3) --> 4105 (2) Reducing 1 from 1 toward 4105
    1 (3) --> 4106 (2) Reducing 1 from 1 toward 4106
    1 (3) --> 4107 (2) Reducing 0 from 1 toward 4107
    1 (3) --> 4108 (2) Reducing 0 from 1 toward 4108
    Excess = 0
    Queue = 4105 4106 4107 4108
================
Active node 4105 Height = 2 IN = 3 OUT = 2
    4105 (2) --> source (3) not eligible
    4105 (2) --> 1 (3) not eligible
    Excess = 1    4105 goes back onto queue with label 3
    Queue = 4106 4107 4108 4105
================
Active node 4106 Height = 2 IN = 1 OUT = 0
    4106 (2) --> source (3) not eligible
    4106 (2) --> 1 (3) not eligible
    4106 (2) --> 5 (1) pushing 1 from 4106 toward 5
    Excess = 0
    Queue = 4107 4108 4105 5
================
Active node 4107 Height = 2 IN = 1 OUT = 1
    4107 (2) --> source (3) not eligible
    4107 (2) --> 2 (1) pushing 0 from 4107 toward 2
    4107 (2) --> 3 (1) pushing 0 from 4107 toward 3
    4107 (2) --> 4 (1) pushing 0 from 4107 toward 4
    4107 (2) --> 6 (1) pushing 0 from 4107 toward 6
    Excess = 0
    Queue = 4108 4105 5 2 3 4 6
================
Active node 4108 Height = 2 IN = 3 OUT = 3
    4108 (2) --> source (3) not eligible
    4108 (2) --> 5 (1) pushing 0 from 4108 toward 5
    4108 (2) --> 6 (1) pushing 0 from 4108 toward 6
    Excess = 0
    Queue = 4105 5 2 3 4 6
================
Active node 4105 Height = 3 IN = 3 OUT = 2
    4105 (3) --> source (3) not eligible
    4105 (3) --> 1 (3) not eligible
    Excess = 1    4105 goes back onto queue with label 4
    Queue = 5 2 3 4 6 4105
================
Active node 5 Height = 1 IN = 1 OUT = 0
    5 (1) --> sink (0) pushing 1 from 5 toward sink
    5 (1) --> 4106 (2) not eligible
    Excess = 0
    Queue = 2 3 4 6 4105
================
Active node 2 Height = 1 IN = 1 OUT = 1
    2 (1) --> sink (0) pushing 0 from 2 toward sink
    2 (1) --> 4108 (2) not eligible
    Excess = 0
    Queue = 3 4 6 4105
================
Active node 3 Height = 1 IN = 0 OUT = 0
    3 (1) --> sink (0) pushing 0 from 3 toward sink
    Excess = 0
    Queue = 4 6 4105
================
Active node 4 Height = 1 IN = 2 OUT = 2
    4 (1) --> 4105 (4) not eligible
    4 (1) --> 4108 (2) not eligible
    Excess = 0
    Queue = 6 4105
================
Active node 6 Height = 1 IN = 1 OUT = 1
    6 (1) --> sink (0) pushing 0 from 6 toward sink
    6 (1) --> 4105 (4) not eligible
    Excess = 0
    Queue = 4105
================
Active node 4105 Height = 4 IN = 3 OUT = 2
    4105 (4) --> source (3) Reducing 1 from 4105 toward source
    4105 (4) --> 1 (3) pushing 0 from 4105 toward 1
    Excess = 0
    Queue = 1
================
Active node 1 Height = 3 IN = 2 OUT = 2
    1 (3) --> 4107 (2) Reducing 0 from 1 toward 4107
    1 (3) --> 4108 (2) Reducing 0 from 1 toward 4108
    Excess = 0
    Queue = 4107 4108
================
Active node 4107 Height = 2 IN = 1 OUT = 1
    4107 (2) --> source (3) not eligible
    4107 (2) --> 2 (1) pushing 0 from 4107 toward 2
    4107 (2) --> 3 (1) pushing 0 from 4107 toward 3
    4107 (2) --> 4 (1) pushing 0 from 4107 toward 4
    4107 (2) --> 6 (1) pushing 0 from 4107 toward 6
    Excess = 0
    Queue = 4108 2 3 4 6
================
Active node 4108 Height = 2 IN = 3 OUT = 3
    4108 (2) --> source (3) not eligible
    4108 (2) --> 5 (1) pushing 0 from 4108 toward 5
    4108 (2) --> 6 (1) pushing 0 from 4108 toward 6
    Excess = 0
    Queue = 2 3 4 6 5
================
Active node 2 Height = 1 IN = 1 OUT = 1
    2 (1) --> sink (0) pushing 0 from 2 toward sink
    2 (1) --> 4108 (2) not eligible
    Excess = 0
    Queue = 3 4 6 5
================
Active node 3 Height = 1 IN = 0 OUT = 0
    3 (1) --> sink (0) pushing 0 from 3 toward sink
    Excess = 0
    Queue = 4 6 5
================
Active node 4 Height = 1 IN = 2 OUT = 2
    4 (1) --> 4105 (4) not eligible
    4 (1) --> 4108 (2) not eligible
    Excess = 0
    Queue = 6 5
================
Active node 6 Height = 1 IN = 1 OUT = 1
    6 (1) --> sink (0) pushing 0 from 6 toward sink
    6 (1) --> 4105 (4) not eligible
    Excess = 0
    Queue = 5
================
Active node 5 Height = 1 IN = 1 OUT = 1
    5 (1) --> sink (0) pushing 0 from 5 toward sink
    5 (1) --> 4106 (2) not eligible
    Excess = 0
    Queue =

【问题讨论】:

  • 您可能想要更改标题。我不认为它反映了帖子的质量——通常是“我的代码正确吗?”标题的问题很糟糕
  • 感谢您的建议。我会考虑一个新的标题。无论如何,如果你能帮我提个建议,我会更加感谢你
  • 这是一个更好的堆栈交换网站,适合此类帖子。codereview.stackexchange.com
  • 我认为您应该找到一个较小的反例。只需编写一个生成器和一个更简单的最大流量实现。由于您的问题在于流量的价值而不是容量,因此任何最大流量算法都可以工作。

标签: c++ network-flow


【解决方案1】:

您的问题

预流

source 没有明确的预流阶段。它包含在while 中,并且在谓词P &gt; 0 &amp;&amp; v == strue 时首先执行一次。也许这样做是为了缩短代码

是的,我认为这样做是为了压缩代码。由于分配wt[s] = Edge.M*G.V() 在开始时源有足够的“虚拟”过剩,以便在算法的第一次迭代中为预流提供燃料。如果你想在你的实现中做同样的技巧,当你遇到源节点时,你将不得不在第一次迭代期间膨胀excess 变量(你即时计算而不是将它存储在像 Sedgewick 这样的数组中) .但是您不必那样做,您的显式预泛洪似乎很好,甚至可能使事情更具可读性。

我还怀疑在条件 P &gt; 0 &amp;&amp; v == s || h[v] == h[w]+1 中隐式运算符优先级 (P &gt; 0 &amp;&amp; v == s) || h[v] == h[w]+1 不是有意的。它的编写方式,检查P &gt; 0 仅针对源案例执行。在其他情况下不检查它不会受到伤害,因为使用 P == 0 执行主体只会将 0 多余的值推送到其他节点(这不会做任何事情)并且不必要地将这些其他节点添加到队列中,只是为了它们立即再次删除。我已经看到您以相同的方式实现它。没关系,即使稍微浪费了计算时间。可能P &gt; 0 &amp;&amp; (v == s || h[v] == h[w]+1) 真的是故意的。

在队列中下沉

根据我的理解和书中的论述,sink 永远不会进入队列。但是,当高度增加时,代码会检查 v != t。这有什么原因吗?

我同意,这很奇怪。接收器进入队列的唯一方法是同时成为源(在只有一个节点且没有边的图中)。但是,在这种情况下,条件v != s 已经避免了无限循环,不需要额外的条件。

错误的结果

执行后,产生的流程如下[...] 有了这个流程,流量值为8,但最大值为9

初始高度

您得到错误的结果是因为您的初始高度错误。我无法将 Sedgewick 的初始化代码与您的进行比较,因为您的帖子也没有指定。但我从您的日志文件中了解到,您从 source 的高度开始为 3。这显然与the conditions for height functions 背道而驰:source 必须以等于图中节点数的高度开始,并将在整个算法期间保持它。

在您的情况下,source 的高度太接近其下游邻居的高度。这会导致下游邻居很快将一些流量推回源头,然后再努力让它流向下游。他们应该这样做只有如果没有办法让它流到水槽。

破图?

让我更担心的是,[4104 -&gt; 1] 的优势似乎消失了。虽然它在节点 4104 的处理过程中被提及,但它在节点 1 的处理过程中从未出现。所有其他传入边都被提及,无论是关于“不合格”、“推送”还是“减少”。我希望它像其他类型一样显示为三种类型之一。但是话又说回来,我不知道您将这些日志记录语句放在哪里。尽管如此,它还是让人有点担心,我想我会提一下,以防你在固定身高后仍然遇到问题。

【讨论】:

  • 非常感谢您花时间详细分析我的问题的答案。你是对的。错误在于初始化高度的方式。这些被初始化为与接收器的弧的最小距离。 Sedgewick 在函数initheights() 以及其他作者中建议了这种方式。但是,源除外,它永远不能处于活动状态。因此,您的更正是在 |V| 中初始化源代码很好。现在实现正确计算了最大流量。
  • 我不认为 Sedgewick 错了,但我认为他的文字可能会误导。他将使用接收器中的广度优先搜索来实施initheights() 作为练习。他的代码没有明确地初始化源代码,在他的演讲中他有时会处理源代码,我会说,不清楚。例如,在他关于高度值的推论中,他说“源的高度永远不会改变,它最初不大于 V”。这是正确的,但我认为他必须更明确。例如,Arjuna 等人在他们的算法中非常明确地初始化 |V| 中的源高度
  • 希望这个问题可以作为一个教训。再次感谢您的回答
  • 我刚刚接触到他的文字,我确实认为他错了:他证明了算法的正确性,并且只使用了 h 的 other 属性(不使用 h (s)=|V|)。这显然是不够的,否则你的实现会奏效。所以他的证明一定有缺陷。我在他的属性 22.11 证明中看到了缺陷,他没有正确区分“残差网络中的边缘”和“资源网络中的合格边缘”。他说 h(u) 增加了,因为没有 eligible 边,但随后他使用了没有 eligible 限制的事实。
猜你喜欢
  • 2011-06-26
  • 2013-03-06
  • 1970-01-01
  • 2015-09-21
  • 2017-06-26
  • 1970-01-01
  • 1970-01-01
  • 2019-09-19
  • 1970-01-01
相关资源
最近更新 更多