Блог пользователя lotusblume

Автор lotusblume, 14 месяцев назад, По-английски

Maximum flow algorithms are known to perform significantly better in practice than their worst-case time complexities would suggest. So in this blog, I will present graphs on which the following maximum flow algorithms actually achieve their worst-case running time:

Algorithm Time Complexity
Ford-Fulkerson with DFS $$$\mathcal{O}\big(m^2 \cdot U)$$$
Edmonds-Karp $$$\mathcal{O}\big(n \cdot m^2\big)$$$
Dinic $$$\mathcal{O}\big(n^2 \cdot m\big)$$$
DFS with Scaling $$$\mathcal{O}\big(m^2 \cdot \log(U)\big)$$$
Dinic with Scaling $$$\mathcal{O}\big(n \cdot m \cdot \log(U)\big)$$$
Most Improving Augmenting Paths $$$\mathcal{O}\big(m \cdot \log(U) \cdot (m + n \cdot \log(n))\big)$$$
FIFO Preflow Push $$$\mathcal{O}\big(n^3\big)$$$
Highest-Label Preflow Push $$$\mathcal{O}\big(n^2 \cdot \sqrt{m}\big)$$$

Here and in the rest of this blog:

  • $$$n =$$$ number of vertices,
  • $$$m =$$$ number of edges,
  • $$$U =$$$ maximum edge capacity.

You can find generators for all the graphs presented in this blog, as well as implementations of all the algorithms used for testing, here: https://github.com/leonard-weininger/worst-case-max-flow/.

1 Benchmark Graphs

The following two graphs will only serve as benchmark graphs when testing the running times on the actual worst-case graphs. Both are frequently used in studies to benchmark maximum flow algorithms.

Genrmf

This graph was created by Goldfarb and Grigoriadis (1988) [1]. It consists of $$$b$$$ frames. Each frame is an $$$a \times a$$$ grid. Within each frame, neighboring vertices are connected in both directions by an edge of capacity $$$1000 \cdot a^2$$$. Between consecutive frames $$$f_i$$$ and $$$f_{i+1}$$$, there are $$$a^2$$$ forward edges from the vertices of $$$f_i$$$ to a random permutation of the vertices of $$$f_{i+1}$$$. Additionally there are $$$a^2$$$ backward edges from $$$f_{i+1}$$$ to a different random permutation of $$$f_i$$$. The capacities of these in-between edges are chosen uniformly at random from the range $$$[1, 1000]$$$.

AK

This graph was created by Cherkassky and Goldberg (1997) [2]. The following figure should describe the graph well.

About Benchmarking

For testing, I always set the maximum number of edges twice as high as the maximum number of vertices. Since Genrmf and AK have slightly different ratios ($$$m \approx 6n$$$ for Genrmf and $$$m \approx \frac{3}{2} n$$$ for AK), I will give them an unfair advantage: They only have to meet one of the bounds, either the vertex bound or the edge bound. So, for example, for $$$n \leq 1000$$$ and $$$m \leq 2000$$$, Genrmf is allowed to generate a graph with $$$n \approx 1000$$$ and $$$m \approx 6000$$$ and AK is allowed to generate a graph with $$$n \approx 1333$$$ and $$$m \approx 2000$$$. All other graphs must always satisfy both the vertex and the edge bounds.

The maximum edge capacity is always chosen as $$$10^9$$$.

For all graphs or algorithms where randomization is involved, the reported running times are averages over $$$10$$$ runs (on $$$10$$$ different random graphs).

Ford-Fulkerson with DFS

Implementing the Ford-Fulkerson algorithm using depth-first search to find augmenting paths is probably the simplest approach to solving the maximum flow algorithm. To be honest, I do not know graphs on which this algorithm requires $$$\Omega(m^2 \cdot U)$$$ time. However, on the following graphs, the running time is exponential, which should be bad enough. The graphs were created by Dean, Goemans and Immorlica (2006) [3].

I assume that the DFS prioritizes vertices with smaller indices. If instead it prioritizes edges based on their order in the input, the same behavior can be achieved by sorting the input edges lexicographically.

Randomized DFS

Ok, DFS with a predictable fixed order of the vertices can be exploited. But maybe randomly shuffling the adjacency lists before the start of the algorithm helps? Not really. You can still enforce the same behavior with high probability. Assume you have a vertex $$$a$$$ with two neighbors $$$b$$$ and $$$c$$$ and you want the randomized DFS to prioritize $$$b$$$ over $$$c$$$. Then you can replace $$$a$$$ with a path of vertices, all of which have an outgoing edge to $$$b$$$ and only the last one has an outgoing edge to $$$c$$$. The probability that $$$b$$$ is prioritized over $$$c$$$ will be $$$1 - 2^{-k}$$$, where $$$k$$$ is the length of the path.

Using this technique, we can recursively define bad graphs $$$G_{i,k}$$$ as seen in the figure below. The probability that the randomized DFS requires $$$2^i$$$ augmenting paths to compute the maximum flow on $$$G_{i,k}$$$ is $$$(1 - 2^{-k})^{4i}$$$.

Shortest Augmenting Paths (Edmonds-Karp, Dinic)

A worst-case graph for Edmonds-Karp and Dinic must require $$$\Omega(n \cdot m)$$$ shortest augmenting paths. The following graph is based on the graph by Zadeh (1972) [4].

Initially the shortest augmenting path is of the form

$$$s_1\rightarrow p_1\rightarrow a_i\rightarrow b_j\rightarrow q_1\rightarrow t_1.$$$

There are $$$k^2$$$ such paths, each with a residual capacity of one. After all these paths are found, the edges $$$(s_1,p_1)$$$, $$$(q_1,t_1)$$$ and all edges $$$(a_i,b_j)$$$ are saturated. Meaning they are reversed in the residual graph. Therefore the next $$$k^2$$$ shortest augmenting paths will be of the form

$$$s_1\rightarrow s_2\rightarrow s_3\rightarrow p_2 \rightarrow b_j\rightarrow a_i\rightarrow q_2\rightarrow t_3 \rightarrow t_2\rightarrow t_1.$$$

This repeats $$$l$$$ times. In total the number of augmenting paths is $$$l\cdot k^2$$$. So by choosing $$$k = \Theta(\sqrt{m})$$$ and $$$l = \Theta(n)$$$, we obtain a graph with $$$\mathcal{O}(n)$$$ vertices, $$$\mathcal{O}(m)$$$ edges and the required number of shortest augmenting paths is $$$\Omega(n \cdot m)$$$.

DFS with Scaling

This algorithm initializes a threshold $$$\Delta$$$ as a large power of two, for example $$$\Delta = 2^{\lfloor\log(U)\rfloor}$$$. It then finds augmenting paths with a DFS by only considering edges with residual capacity of at least $$$\Delta$$$. When no more such path exists, you divide $$$\Delta$$$ by two, and continue. When $$$\Delta = 1$$$ and no more augmenting paths exist, the maximum flow has been found. You can prove, that this strategy requires at most $$$\mathcal{O}(m \cdot \log(U))$$$ augmenting paths.

To achieve $$$\Omega(m \cdot \log(U))$$$ augmenting paths, we start with a complete bipartite graph containing $$$w^2$$$ edges, each of capacity $$$2^p-1$$$. The first threshold for which a path through the bipartite graph exists, is $$$\Delta = 2^{p-1}$$$. We want to have $$$w^2$$$ of those paths and we want that each of them has a capacity of exactly $$$2^{p-1}$$$. Therefore we add a copy of the Anti-DFS graph, which I described earlier. In this Anti-DFS graph, we increase all capacities by $$$2^{p-1}$$$ and make it large enough such that at least $$$w^2$$$ augmenting paths can be sent through it. Similarly we add Anti-DFS graphs for all thresholds $$$2^{p-2}, \dots, 2^0$$$.

In total, the DFS with Scaling algorithm requires $$$\Omega(w^2 \cdot p)$$$ augmenting paths, to find the maximum flow in this graph. So by setting $$$w = \Theta(\sqrt{m})$$$ and $$$p = \Theta(\log U)$$$ we achieve $$$\Omega(m \cdot \log(U))$$$ augmenting paths.

Additional Constraints

Dinic with Scaling

This is the same algorithm, as DFS with Scaling, only that the paths are found with Dinic (i.e., shortest paths) instead of depth-first search. Therefore the worst-case graph is also the same, only with Anti-Shortest-Paths-Graphs chained together instead of Anti-DFS graphs.

Additional Constraints

Most Improving Augmenting Paths

The Most Improving Augmenting Paths algorithm always chooses augmenting paths with the largest residual capacity. Similar to the capacity scaling algorithm, this strategy requires at most $$$\mathcal{O}(m \cdot \log(U))$$$ augmenting paths. The following graph was created by Queyranne (1980) [5].

First consider this graph with $$$10$$$ vertices and $$$17$$$ edges:

The most improving augmenting paths algorithm will find the following six augmenting paths:

Iteration Path Residual Capacity
1 $$$s \rightarrow a \rightarrow f \rightarrow g \rightarrow b \rightarrow h \rightarrow c \rightarrow d \rightarrow t$$$ 8
2 $$$s \rightarrow f \rightarrow a \rightarrow g \rightarrow b \rightarrow c \rightarrow h \rightarrow t$$$ 5
3 $$$s \rightarrow e \rightarrow f \rightarrow a \rightarrow b \rightarrow g \rightarrow h \rightarrow c \rightarrow t$$$ 3
4 $$$s \rightarrow a \rightarrow f \rightarrow g \rightarrow b \rightarrow h \rightarrow c \rightarrow d \rightarrow t$$$ 2
5 $$$s \rightarrow f \rightarrow a \rightarrow g \rightarrow b \rightarrow c \rightarrow h \rightarrow t$$$ 1
6 $$$s \rightarrow e \rightarrow f \rightarrow a \rightarrow b \rightarrow g \rightarrow h \rightarrow c \rightarrow t$$$ 1

You may notice a pattern. The residual path capacities are decreasing Fibonacci numbers. And the first three paths repeat themselves. In fact this pattern can be continued arbitrarily often just by increasing the edge capacities. Choosing the capacities as follows will cause $$$3k$$$ most improving augmenting paths.

Edges Capacity
$$$sa, fg, bh, cd, dt$$$ $$$\sum_{i=1}^{k} F_{3i}$$$
$$$sf, ag, bc, ht$$$ $$$\sum_{i=1}^{k} F_{3i-1}$$$
$$$se, ef, ab, gh, ct$$$ $$$\sum_{i=1}^{k} F_{3i-2}$$$
$$$af, hc$$$ $$$F_{3k}$$$
$$$gb$$$ $$$F_{3k+1}$$$

Here $$$F_i$$$ are the Fibonacci numbers (i.e., $$$F_0 = 0$$$, $$$F_1 = 1$$$ and $$$F_i = F_{i-2} + F_{i-1}$$$ for $$$i \geq 2$$$). Since the Fibonacci numbers grow exponentially, the number of augmenting paths will be $$$\Omega(\log{U})$$$. In theory, to achieve $$$\Omega(m \cdot \log{U})$$$ augmenting paths, we could replace each edge with $$$\Omega(m)$$$ parallel edges. Or we could replace each vertex with a group of $$$\Omega(\sqrt{m})$$$ vertices and add complete bipartite graphs between the groups, instead of the original edges. However both these methods do not work in practice when the most improving paths are found with Dijkstra. Therefore in practice, the best graph, that I know of, achieves $$$\Omega(n \cdot \log{U})$$$ augmenting paths, by using $$$\Omega(n)$$$ copies of the above graph.

First-In-First-Out Preflow Push (FIFOPP)

The following graph is inspired by the work of Cheriyan and Maheshwari (1989) [6]. I will only describe the general behavior of FIFOPP on the graph, and the behavior in practice is somewhat more complex. Please note that for the graph to work, the priority of the edges matters.

First, consider the following graph:

Initially a large flow excess is pushed into $$$p$$$. All this flow will then be pushed into $$$a_1$$$ and a single flow unit will be pushed to $$$b_1$$$, then to $$$q$$$ and then through the path of length $$$k$$$ toward $$$t$$$. The big flow excess that remained in $$$a_1$$$ is meanwhile pushed back to $$$p$$$ and then into $$$a_2$$$. Now another unit of flow is sent toward $$$t$$$. Overall $$$k$$$ units of flow are pushed toward $$$t$$$, so the total number of pushes is $$$\Omega(k^2) = \Omega(n^2)$$$.

To achieve a running time of $$$\Omega(n^3)$$$ we will reuse this structure $$$\Omega(n)$$$ times. To do that, we increase the length of the path and add edges from $$$q$$$ to every eighth vertex on the path. Each of capacity $$$k$$$. We do the same for vertex $$$p$$$, only the target vertices on the path are offset by four. And finally we set the capacity the edge $$$a_kb_k$$$ to infinity. The large flow excess will go back and forth between $$$p$$$ and $$$q$$$, a total of $$$k$$$ times. Each time it tries to go from $$$p$$$ to $$$q$$$ it will send $$$k$$$ unit flows toward $$$t$$$. The total required time is therefore $$$\Omega(k^3) = \Omega(n^3)$$$. (It is important, that the algorithm prioritizes $$$a_1 \dots a_{k-1}$$$ over $$$a_k$$$ and $$$b_1 \dots b_{k-1}$$$ over $$$b_k$$$).

Highest-Label Preflow Push (HLPP)

Again, the following graph is inspired by the work of Cheriyan and Maheshwari (1989) [6]. Again, I will only describe the general behavior of HLPP on the graph, and the behavior in practice is somewhat more complex. And again, please note that for the graph to work, the priority of the edges matters.

First consider the following gadget:

Assume a flow excess of value $$$w$$$ enters $$$p_1$$$. It will be pushed into $$$c_1$$$ and is then split up into $$$w$$$ flows of value $$$1$$$, which are pushed into the vertices $$$d_1, \dots, d_w$$$. Now they are pushed through $$$q_2$$$ toward $$$t$$$, causing a total of $$$w \cdot k$$$ pushes. This can repeat $$$w$$$ times, once for each $$$c_1, \dots, c_w$$$. After that, the edge $$$(q_2, t)$$$ and all edges $$$(c_i, d_j)$$$ are saturated.

Now assume a flow excess of value $$$w$$$ enters $$$p_2$$$. The shortest path to push it toward $$$t$$$ will be through $$$d_1$$$ and then to split up into $$$w$$$ flows of value $$$1$$$ into the vertices $$$c_1, \dots, c_w$$$ and then through $$$q_1$$$ toward $$$t$$$. Of course, the HLPP algorithm will not immediately realize that this is the shortest path. But after pushing the flow back and forth between $$$p_2$$$ and $$$q_2$$$ a few times, it will at some point push everything into $$$c_1, \dots, c_w$$$ and behave as described. (Here it is important that the algorithm prioritizes edges that lead away from $$$t$$$, i.e., pointing upward in the figure). So in total there are again $$$k \cdot w$$$ pushes. And again this can be repeated $$$w$$$ times, once for each $$$d_1, \dots, d_w$$$.

After that, there can again be a sequence of $$$w$$$ pushes of value $$$w$$$ into $$$p_1$$$, and then again a sequence of $$$w$$$ pushes of value $$$w$$$ into $$$p_2$$$. And so on.

We will choose $$$w = \Theta(\sqrt{m})$$$ and $$$k = \Theta(n / \sqrt{m})$$$. Therefore, if we are able to send $$$\Omega(n)$$$ sequences of $$$w$$$ pushes, each of value $$$w$$$, into $$$p_1$$$ and $$$p_2$$$ (alternating between them), then this will result in a total of $$$\Omega(n \cdot w \cdot w \cdot k) = \Omega(n^2 \cdot \sqrt{m})$$$ pushes.

To achieve these $$$\Omega(n)$$$ sequences of $$$w$$$ pushes into $$$p_1$$$ and $$$p_2$$$, we start by adding the following side gadgets:

The source will push an excess of value $$$w^2$$$ into $$$a_1$$$. Which will then be split into $$$w$$$ pushes of value $$$w$$$ through the side gadget into $$$b_1$$$. There it will realize it is in a dead end and the algorithm will one by one push each of these $$$w$$$ flow excesses back through $$$a_1$$$ and then into $$$p_1$$$ exactly as we wanted. After this process, the side gadget is empty again and could be reused.

Ideally we want to have more than one source vertex. Initially one source vertex should send a flow of $$$w^2$$$ into $$$a_1$$$. Then after all this flow has been processed, a second source should get activated that sends a flow of $$$w^2$$$ into $$$a_2$$$. This should repeat $$$\Omega(n)$$$ times, alternating between pushing flow into $$$a_1$$$ and into $$$a_2$$$.

We can actually simulate this behavior of having multiple sources. The real source $$$s$$$ will be connected to $$$2l$$$ vertices which serve as the auxiliary sources. We add an edge from these auxiliary sources directly to $$$t$$$. This has the effect that their height label is $$$1$$$. Meaning they will always have the lowest label (i.e., the lowest priority to be chosen by HLPP). So HLPP will only activate a new auxiliary vertex once all flow from the previous source has been fully processed. See the figure below for the final Anti-HLPP graph. By choosing $$$l = \Theta(n)$$$, $$$k = \Theta(n / \sqrt{m})$$$ and $$$w = \Theta(\sqrt{m})$$$, we obtain a graph with $$$\mathcal{O}(n)$$$ vertices and $$$\mathcal{O}(m)$$$ edges. And HLPP requires at least $$$2l \cdot w \cdot w \cdot k = \Omega(n^2 \cdot \sqrt{m})$$$ pushes.

Reasons why the performance on the Anti-HLPP graph is not worse than on Genrmf:

  • Genrmf has the property that more flow is sent deep into the graph, than can ultimately be pushed into $$$t$$$. To push this flow back to $$$s$$$ the algorithm must relabel a lot of vertices $$$\Omega(n)$$$ times. This results in a running time of $$$\Omega(n \cdot m)$$$.
  • Remember that I allowed Genrmf to use about $$$3$$$ times more edges than Anti-HLPP.
  • The constant factor of Anti-HLPP is very low (about $$$\frac{1}{150}$$$).

And the graph sizes are not large enough, such that $$$\frac{1}{150} n^2 \cdot \sqrt{m}$$$ beats $$$3 \cdot n \cdot m$$$.

HLPP with Global Relabeling and Gap Heuristics

The Global Relabeling heuristic and the Gap heuristic do not help on the Anti-HLPP graph. However, they help a lot on Genrmf.

Comparing all Algorithms

Of course, all these graphs represent only asymptotic worst-case scenarios. It is quite likely that other worst-case graphs exist with much higher constant factors.

Thanks for reading. If you know about any other bad maximum flow graphs, I would love to know about them :)


References
  • Проголосовать: нравится
  • +927
  • Проголосовать: не нравится

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится +79 Проголосовать: не нравится

💀

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится +8 Проголосовать: не нравится

orz

»
14 месяцев назад, скрыть # |
← Rev. 2  
Проголосовать: нравится -21 Проголосовать: не нравится

This blog offers an insightful and rigorous exploration of worst-case graphs for various maximum flow algorithms, highlighting the deep gap between theoretical complexity and practical performance.

Thanks for posting

Pov: My mind is not working after reading this blog .

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится +11 Проголосовать: не нравится

Ford-Fulkerson using DFS, does one DFS search from the source, and then it increases the flow by at least $$$1$$$, or it terminates. Shouldn't its complexity be $$$O((n+m)U)$$$?

  • »
    »
    14 месяцев назад, скрыть # ^ |
     
    Проголосовать: нравится +19 Проголосовать: не нравится

    $$$U$$$ is the limit on one edge capacity, maxflow is (theoretically) $$$mU$$$.

    • »
      »
      »
      14 месяцев назад, скрыть # ^ |
      ← Rev. 2  
      Проголосовать: нравится +11 Проголосовать: не нравится

      Oops yes. That upperbound seems indeed hard to achieve. You kinda need multiedges for it. But maybe it's almost achievable with the construction in the blog. This conatruction uses $$$O(\log(U))$$$ edges. But by using multiedges we can build more levels on top of this construction (using $$$2^k$$$ parallel, weight $$$U$$$ edges). We can build $$$\log_2(m) - O(1)$$$ levels more. This means the construction can send $$$O(m U)$$$ units of flow one at a time, and then each time at the top level you take at least $$$O(m)$$$ looping through all the multiedges.

      • »
        »
        »
        »
        14 месяцев назад, скрыть # ^ |
        ← Rev. 4  
        Проголосовать: нравится +21 Проголосовать: не нравится

        Wow, I have never realized that this extension results in $$$\Omega(mU)$$$ paths. Then the construction does actually achieve the worst case.

        It is not true $$$\Omega(mU)$$$, but rather $$$\Omega$$$(min($$$mU, 2^{m/4}$$$)). So we can not get arbitrarily many paths for a fixed $$$m$$$ by just increasing $$$U$$$.

        But getting arbitrarily many paths for DFS, by just increasing $$$U$$$ is impossible. Because you can proof that the DFS algorithm will never find the same path twice. (In that sense DFS is even superior to Most-Improving-Augmenting-Paths).

        Proof
»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится +145 Проголосовать: не нравится

This is amazing. Thank you very much.

The graphs could be more informative, though. Log scale, or if you want to show that the complexity is a specific function, you can make the ordinate scale as that function, so the points will be on a line.

»
14 месяцев назад, скрыть # |
← Rev. 2  
Проголосовать: нравится +18 Проголосовать: не нравится

Illustrations in your blog look incredible! You made them in LaTeX or with some other tool (I'm talking about graph structure ones, of course)?

And blog in general is very good, thank you for your hard work!

  • »
    »
    14 месяцев назад, скрыть # ^ |
    ← Rev. 2  
    Проголосовать: нравится +6 Проголосовать: не нравится

    Thank you, I am glad you liked it.

    Yes, I created the graphs with LaTeX, TikZ. Here is for example the code for the Anti-Shortest-Paths graph:

    anti_shortest_paths.tex

    Edit: It seems like all '$' are displayed as '$$$'. But they should all be single '$'.

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится +21 Проголосовать: не нравится

Thank you very much, lotusblume. This is really an amazing blog! Very nice study and great descriptions.

As a problemsetter, this will be very useful for me as a reference when preparing flow problems in the future :)

»
11 месяцев назад, скрыть # |
 
Проголосовать: нравится +1 Проголосовать: не нравится

Amazing work!

»
11 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

If I don't remember it wrong, I believe there is one approach using LCT + dinic and it could potentially get to $$$O(nmlogn)$$$.

»
7 месяцев назад, скрыть # |
 
Проголосовать: нравится -21 Проголосовать: не нравится

I wish I could get as skilled as you one day , but also how did you create those performance visualizations , python + matplotlib ?

»
7 месяцев назад, скрыть # |
← Rev. 2  
Проголосовать: нравится 0 Проголосовать: не нравится

What tool did you use to draw the line graph? It looks pretty.