In which we discuss the worst-case running of the Ford-Fulkerson algorithm, discuss plausible heuristics to choose an augmenting path in a good way, and begin analyzing the “fattest path” heuristic.
In the last lecture we proved the Max-Flow Min-Cut theorem in a way that also established the optimality of the Ford-Fulkerson algorithm: if we iteratively find an augmenting path in the residual network and push more flow along that path, as allowed by the capacity constraints, we will eventually find a flow for which no augmenting path exists, and we proved that such a flow must be optimal.
Each iteration of the algorithm takes linear time in the size of the network: the augmenting path can be found via a DFS of the residual network, for example. The problem is that, in certain cases, the algorithm might take a very long time to finish. Consider, for example, the following network.
Suppose that, at the first step, we pick the augmenting path . We can only push one unit of flow along that path. After this first step, our residual network (not showing edges out of
and into
, which are never used in an augmenting path) is
Now it is possible that the algorithm picks the augmenting path along which, again, only one unit of flow can be routed. We see that, indeed, it is possible for the algorithm to keep picking augmenting paths that involve a link between
and
, so that only one extra unit of flow is routed at each step.
The problem of very slow convergence times as in the above example can be avoided if, at each iteration, we choose more carefully which augmenting path to use. One reasonable heuristic is that it makes sense to pick the augmenting path along which the most flow can be routed in one step. If we had used such an heuristic in the above example, we would have found the optimum in two steps. Another, alternative, heuristic is to pick the shortest augmenting path, that is, the augmenting path that uses the fewest edges; this is reasonable because in this way we are going to use the capacity of fewer edges and keep more residual capacity for later iterations. The use of this heuristic would have also resulted in a two-iterations running time in the above example.
1. The “fattest” augmenting path heuristic
We begin by studying the first heuristic: that is we consider an implementation of the Ford-Fulkerson algorithm in which, at every iteration, we pick a fattest augmenting path in the residual network, where the fatness of a path in a capacitated network is the minimum capacity of the edges in the path. In the network of our previous example, the paths and
have fatness
, while the path
has fatness
.
How do we find a fattest augmenting path? We will show that it can be found with a simple modification of Dijkstra’s algorithm for finding shortest paths.
1.1. Dijkstra’s algorithm
Let us first quickly recall how Dijkstra’s algorithm works. Suppose that we have a graph in which each edge has a length
and, for two given vertices
, we want to find the path of minimal length from
to
, where the length of a path is the sum of the lengths of the edges in the path. The algorithm will solve, for free, the more general problem of computing the length of the shortest path from
to
for every vertex
. In the algorithm, the data structure that holds information about a vertex
has two fields:
, which will eventually contain the length of the shortest path from
to
, and
which will contain the predecessor of
in a shortest path from
to
, that is, the identity of the vertex that comes immediately before
in such a path.
The distances are initialized to , except for
which is initialized to zero. The algorithm initially puts all vertices in a priority queue
. Recall that a priority queue is a data structure that contains elements which have a numerical field called a key (in this case the key is the dist field), and that supports the operation of inserting an element in the queue, of finding and removing from the queue the element of minimal key value, and of reducing the key field of a given element.
The algorithm works as follows:
Algorithm Dijkstra
- Input: graph
, vertex
, non-negative edge lengths
- for each
, let
-
- insert all vertices in a priority queue
keyed by the dist field
- while
is not empty
- find and remove vertex
in
whose field
is smallest among queue elements
- for all vertices
such that
- if
then
-
- update
to reflect changed value of
-
-
- if
- find and remove vertex
The running time is equal to whatever time it takes to execute insert operations,
remove-min operations, and
reduce-key operations in the priority queue. The simple implementation of a priority queue via a binary heap gives
running time for each operation, and a total running time of
. A more elaborate data structure called a Fibonacci heap implements insert and remove-min in
time, and is such that
decrease-key operations always take at most
time overall, so that the total running time is
.
Regarding correctness, we can prove by induction that the algorithm maintains the following invariant: at the beginning of each iteration of the while loop, the vertices which are not in the queue are such that
is the correct value of the shortest path length from
to
and such a shortest path can be realized by combining a shortest path from
to
and then continuing with the edge
. This is certainly true at the beginning, because the first vertex to be removed is
, which is at distance
from itself, and if it is true at the end, when the queue is empty, it means that at the end of the algorithm all vertices get their correct values of
and
. So we need to show that if the invariant is true at a certain step then it is true at the following step.
Basically, all we need to prove is that, at the beginning of each iteration, the vertex that we remove from the queue has correct values of
and
. If we call
, then
is a vertex that was removed from the queue at an earlier iteration and so, by the inductive hypothesis, is such that
is the correct shortest path distance from
to
; if
we also have
, which means that there is indeed a path of length
from
to
in which
is the predecessor of
. We need to prove that this path is a shortest path. So suppose toward a contradiction that there is a shorter path
of length
. The path
starts at
, which is outside the queue, and ends at
, which is in the queue, so at some point the path must have an edge
such that
is outside the queue and
is inside. This also means that when
was removed from the queue it had the correct value
, and after we processed the neighbors of
we had
. But this would mean that
is at most the length of the path
, while
is bigger than the length of the path
, which is impossible because
was chosen to be the element with the smallest
among elements of the queue.
1.2. Adaptation to find a fattest path
What would be the most straightforward adaptation of Dijkstra’s algorithm to the problem of finding a fattest path? In the shortest path problem, the length of a path is the sum of the lengths of the edges of the path, and we want to find a path of minimal length; in the fattest path problem, the fatness of a path is the minimum of the capacities of the edges of the path, and we want to find a path of maximal fatness. So we just change sums to , lengths to capacities, and minimization to maximization.
Algorithm Dijkstra-F
- Input: graph
, vertex
, non-negative edge capacities
- for each
, let
-
- insert all vertices in a priority queue
keyed by the dist field
- while
is not empty
- find and remove vertex
in
whose field
is largest among queue elements
- for all vertices
such that
- if
then
-
- update
to reflect changed value of
-
-
- if
- find and remove vertex
The running time is the same and, quite amazingly, the proof of correctness is also essentially the same. (Try writing it up.)
Remark 1 A useful feature of Dijkstra’s algorithm (and other shortest path algorithms) is that it works to find “best” paths for a lot of different measures of “cost” for a path, besides length and fatness. Basically, the only requirements to implement the algorithm and prove correctness are:
- The cost of a path
is no better than the cost of an initial segment
,
of the path. That is, if we are trying to maximize the cost, we need the property that the cost of a path is at most the cost of any initial segment (e.g., the fatness of a path is at most the fatness of any initial segment, because in the former case we are taking the minimum over a larger set of capacities); if we are trying to minimize the cost, we need the property that the cost of a path is at least the cost of any initial segment.
- The cost of a path
can be determined by only knowing the cost of the path
and the cost of the edge
.
1.3. Analysis of the fattest augmenting path heuristic
In the next lecture, we will prove the following result.
Theorem 1 If
is a network in which the optimal flow has cost
, then there is a path from
to
of fatness
.
From the above theorem, we se that if we implement the Ford-Fulkerson algorithm with the fattest-path heuristic, then, after we have found augmenting paths, we have a solution such that, in the residual network, the optimum flow has cost at most
.
To see why, call the cost of the flow found by the algorithm after
iterations, and
the optimum of the residual network after
iterations of the algorithm. Clearly we have
.
The theorem tells us that at iteration we are going to find an augmenting path of fatness at least
. (Because of the “virtual capacities,” the residual network could have as many as twice the number of edges of the original network, but no more.) This means that the cost of the flow at the end of the
-th iteration is going to be
, which means that the residual optimum is going to be
We started with and
, and so we must have
.
If the capacities are integers, then if the residual network has an optimum less than 1, its optimum must be zero. Recalling that ,
This means that if , then
, which implies
and so it means that, within the first
steps, the algorithm reaches a point in which the residual network has no augmenting path and it stops.
We said that, using the simple binary heap implementation of Dijkstra’s algorithm, the running time of one iteration is , and so we have the following analysis.
Theorem 2 The fattest-path implementation of the Ford-Fulkerson algorithm, given in input a network with integer capacities whose optimal flow has cost
, runs in time at most
To complete the above running time analysis, we need to prove Theorem 1, which we will do next time.
where is “next time” ????
When describing the dijkstra modification, you seem to be alternating between v.fat and v.dist, are these the same?