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
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
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.
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.
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 :)








💀
orz
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 .
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)$$$?
$$$U$$$ is the limit on one edge capacity, maxflow is (theoretically) $$$mU$$$.
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.
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).
We define a total order on $$$s-t$$$ paths: to compare two paths $$$P$$$ and $$$P'$$$, look at the first vertex where their chosen outgoing edges differ, let them be $$$e$$$ and $$$e'$$$ and compare those two edges by their priority in the algorithm. We say $$$e \lt e'$$$ (and consequently $$$P \lt P'$$$) if $$$e$$$ is prioritized over $$$e'$$$.
To proof that no augmenting path appears twice, we show that whole sequence of augmenting paths is strictly increasing.
Let $$$G$$$ be the residual graph before an augmentation, and $$$P=e_1,\dots,e_k$$$ the augmenting path found by the DFS in $$$G$$$. Let $$$G'$$$ be the residual graph after augmenting along $$$P$$$, and let $$$P'=e'_1,\dots,e'_\ell$$$ be the next augmenting path found by the DFS in $$$G'$$$.
First, $$$P'\neq P$$$: along $$$P$$$, at least one forward residual edge becomes saturated (the bottleneck), so that edge is not available in $$$G'$$$, and the identical path cannot reappear immediately.
Let $$$i$$$ be the first index where $$$P$$$ and $$$P'$$$ differ. Then $$$e_j=e'_j$$$ for all $$$j \lt i$$$, and both paths are at the same vertex $$$u$$$ just before choosing their $$$i$$$-th edge. Let $$$e_i=(u,v)$$$ and $$$e'_i=(u,v')$$$. We will show $$$e_i \lt e'_i$$$, and consequently $$$P \lt P'$$$.
Assume for contradiction that $$$e_i \gt e'_i$$$.
Claim 1. The edge $$$e'_i = (u,v')$$$ is already present as a residual edge in $$$G$$$.
Proof of Claim 1. The only residual edges whose status changes from $$$G$$$ to $$$G'$$$ are edges on $$$P$$$ (their forward residual capacity decreases) and their reverses (their reverse residual capacity increases). Thus an edge that was absent in $$$G$$$ but present in $$$G'$$$ must be the reverse of an edge used by $$$P$$$ in the forward direction. If $$$(u,v')$$$ were such a newly created edge, then $$$(v',u)$$$ would lie on $$$P$$$. But $$$P$$$ and $$$P'$$$ share the same prefix up to $$$u$$$, so $$$u$$$ is visited before $$$v'$$$ on $$$P$$$. And since augmenting paths are simple, $$$P$$$ cannot later traverse $$$(v',u)$$$ (that would revisit $$$u$$$). Contradiction. Therefore $$$(u,v')$$$ is already in $$$G$$$.
Let $$$S$$$ be the set of vertices on the prefix of $$$P$$$ from $$$s$$$ up to and including $$$u$$$. Consider the graph $$$G-S$$$ obtained from $$$G$$$ by deleting $$$S$$$. Let $$$W$$$ be the set of vertices reachable from $$$v'$$$ in $$$G-S$$$.
Claim 2. $$$t\notin W$$$.
Proof of Claim 2. When the DFS at $$$u$$$ in $$$G$$$ chooses the next edge, it tries outgoing edges in the fixed order. Since $$$e'_i=(u,v')$$$ is prioritized over $$$e_i=(u,v)$$$ by assumption, if $$$t$$$ were reachable from $$$v'$$$ in $$$G-S$$$, the DFS would have followed $$$(u,v')$$$ and eventually reached $$$t$$$, producing an augmenting path that used $$$(u,v')$$$ instead of $$$(u,v)$$$. That contradicts the choice of $$$P$$$.
Now look at $$$G'$$$ and the path $$$P'$$$. Because $$$P'$$$ is simple and shares the prefix ending at $$$u$$$ with $$$P$$$, the suffix of $$$P'$$$ starting from $$$v'$$$ does not visit any vertex of $$$S$$$. Hence, in $$$G'-S$$$, there is a path from $$$v'$$$ to $$$t$$$. Consequently, in $$$G'-S$$$ there must be at least one edge leaving $$$W$$$ (otherwise $$$t$$$ would still be unreachable from $$$v'$$$ because $$$v' \in W$$$).
But in $$$G-S$$$, by definition of $$$W$$$, there are no residual edges leaving $$$W$$$. Therefore some edge leaving $$$W$$$ is newly present in $$$G'-S$$$ that was not present in $$$G-S$$$.
The only way such a new leaving edge can appear when passing from $$$G$$$ to $$$G'$$$ is as the reverse of an edge of $$$P$$$ that goes into $$$W$$$. However, $$$P$$$ cannot enter $$$W$$$: if $$$P$$$ ever reached a vertex of $$$W$$$ in $$$G-S$$$, then it would be impossible for $$$P$$$ to continue to $$$t$$$. Therefore $$$P$$$ does not touch $$$W$$$, and no new edge leaving $$$W$$$ can be created. Contradiction.
Therefore our assumption was false, and $$$e_i = (u,v)$$$ must be prioritized over $$$e'_i = (u,v')$$$, that is $$$e_i \lt e'_i$$$. Consequently $$$P$$$ < $$$P'$$$. By induction, the whole sequence of augmenting paths is strictly increasing. In particular, no augmenting path can appear twice.
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.
Thank you a lot for the suggestion. I tried it for Dinic and HLPP.
It is really nice to see the straight lines :)
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!
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:
Edit: It seems like all '$' are displayed as '$$$'. But they should all be single '$'.
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 :)
We need more flow problems in codeforces contests :D
I agree!
Amazing work!
If I don't remember it wrong, I believe there is one approach using LCT + dinic and it could potentially get to $$$O(nmlogn)$$$.
I've been told that using LCTs introduces quite a high constant factor, and thus isn't particularly useful for the purposes of competitive programming. Hadn't tried it myself though.
I wish I could get as skilled as you one day , but also how did you create those performance visualizations , python + matplotlib ?
Yes, I used python with matplotlib
What tool did you use to draw the line graph? It looks pretty.
Thank you. For the diagrams that visualize the running times, I used python with matplotlib. For the drawings of the flow-networks, I used tikz.