Data Structures, Algorithms, & Applications in C++
Single Source Shortest Paths
With Negative Edge Costs
Graphs/digraphs With Negative Edge Costs
In a graph/digraph that represents a communication network, the edge costs
may be the cost of going from one city to the next. If we are having
a promotion on a certain route, the cost of the corresponding edge
may be negative. Another application in which some or all edges may
have a negative cost is when the weighted digraph is used
to solve a system of difference equations.
We shall develop this application after we develop a dynamic programming
algorithm to find the shortest paths from a given source vertex s
to the remaining vertices in a weighted digraph with one or more
negative-cost
edges.
The single-source all-destinations greedy algorithm developed
in Section 13.3.5 cannot be used to obtain the shortest paths when
the digraph has one or more negative-cost edges (see solution to
Exercise 13.26). However, the dynamic-programming all pairs shortest-paths
algorithm of Section 15.2.4 can be used because it works so long as the
graph/digraph has no cycle whose length/cost is less than 0. As pointed out
in Section 15.2.4, the shortest-path problem is not well-defined when we have
cycles whose length/cost is negative, because now some shortest paths have
an infinite number of edges on them. Therefore, it is reasonable for
us to assume that the graph/digraph we are dealing with has no
cycle whose length/cost is negative.
Since the dynamic-programming algorithm of Section 15.2.4 finds shortest
paths in a graph/digraph that has negative-cost edges (but no negative-cost
cycle) in
Theta(n3) time, the single-source
all-destinations algorithm that we develop should run in less time than
this in order for it to be a useful algorithm.
As we shall see the complexity of the algorithm we shall develop here
is O(n3) when the adjacency-matrix
representation is used and is
O(ne) when the linked-adjacency-list
representation is used (e is the number of edges).
Therefore, the new algorithm has a run-time advantage over the
algorithm of Section 15.2.4 when we are interested in finding
the shortest paths from a single source vertex.
The new algorithm is due to Bellman and Ford and is known as the Bellman-Ford
algorithm.
Dynamic Programming Formulation
Since the graph/digraph has no negative length/cost cycles, we can
assume that all shortest paths have at most n-1
edges. Note that a path with n or more edges must contain a
cycle; this cycle has length/cost >= 0 and so the path that remains
following the deletion of this cycle is no longer than the original path;
therefore, between any two connected vertices there
is always a shortest path that has no cycle and hence has at most
n-1 edges.
As in Sections 13.3.5 and 15.2.4, we first assume we are interested
only in the lengths of the shortest paths.
Let dk(v) be the length of a shortest path
from the source vertex to vertex v under the
constraint that the path has at most k edges.
We see that dn-1(v) is the length
of a shortest path
from the source vertex to vertex v under no
constraint. This is the answer we seek. Further,
d1(v) is the length
of a shortest path from the source to vertex
v using at most one edge.
Therefore,
d1(v) is the cost of the
edge (s,v) if it exists. If the
edge does not exist,
d1(v) is 0 in case
v = s and is infinity otherwise.
For k > 1, we obtain the following
dynamic-programming recurrence
dk(v) = min {dk-1(u) + cost(u,v) |
(u,v) is an edge of the graph}
This recurrence can be used to compute
dn-1() by computing the
ds in the order
d2(),
d3(), ...
dn-1().
Pseudocode
The pseudocode for the resulting algorithm is given below.
initialize d^1;
// compute remaining d^k's
for (int k = 2; k < n; k++) {
d^k(v) = infinity for all v;
for (each edge (u,v))
d^k(v) = min(d^k(v), d^(k-1)(u) + cost(u,v));
}
Refinements To Pseudocode
Since dk(v) <=
dk-1(v),
dk(v) may be initialized
to
dk-1(v) rather than to infinity.
Another observation is that we can do the computation in-place
as below (this also eliminates the need to initialize
d at the start of each iteration).
initialize d() = d^1;
// compute d^(n-1)
for (int k = 2; k < n; k++)
for (each edge (u,v))
d(v) = min(d(v), d(u) + cost(u,v));
Notice that while the in-place computation does not have
d() = dk
at the end of each iteration of the outer loop, the value of
d() on termination is dn-1.
Basically, the original code computes the min function
n-1 times at each vertex and ensures that
in the end there is no vertex whose d(v) can
be reduced by performing the min function again at this vertex.
The modified in-place code does this also. The assurance that no
further reduction in d follows follows from the
fact that the graph/digraph has no cycle with negative cost.
In fact, on termination, we can check each d(v)
to see if it can be reduced by applying the min operation of the
pseudocode. If any one of the d can be reuced,
the original graph has a cycle of negative length/cost.
Two other observations that can be used to reduce the computation time are
(1) if for some k,
d(v) does not change for any v,
then we can abort the outer for loop,
and (2) the inner
for loop need be done only for those edges
(u,v) for which d(u)
changed on the previous iteration of the outer loop.
Incorporating these observations results in the pseudocode:
initialize d() = d^1;
put the vertices that are adjacent from the source vertex onto a list C1;
// compute d^(n-1)
for (int k = 2; k < n; k++) {
// see if there are vertices whose d value has changed
if (C1 is empty) break; // no such vertex
while (C1 is not empty) {
delete a vertex u from C1;
for (each edge (u,v)) {
d(v) = min(d(v), d(u) + cost(u,v));
if d(v) has changed and v is not on list C2 add v to list C2;
}
swap C1 and C2;
}
C++ Implementation
When implementing th refined pseudocode it is useful to maintain an
array InC2 such that
InC2[v] is true iff vertex
v is on the list
C2. This array needs to be reset to false
at the end of each iteration of the outer for
loop. This resetting is done efficiently by going through the vertices
that are on
C2 and resetting only the corresponding array values.
Although any list structure which permits
constant time insertion and deletion may be used (for example, a stack
or a queue), we shall use a chain bacause we have defined an iterator
for this. Using the iterator, we can reset the array
InC2 easily at the end of each iteration of the
outer for loop.
Our implementation of the Bellman and Ford algorithm
is given below and in the files
bell.*.
This code includes the predecessor array p
as used in shortest-path code of Program 13.5. Using the
terminal p values, we can construct
the shortest paths as described in the solution to Exercise 13.27.
template <class T>
void WNetwork<T>::BellmanFord(int s, T d[], int p[])
{// Shortest paths from vertex s, return shortest
// distances in d and predecessor info in p.
// Graph may have edges with negative cost but should
// not have a cycle with negative cost.
int n = Vertices();
if (s < 1 || s > n) throw OutOfBounds();
InitializePos(); // init graph iterator array
// define two chains for vertices whose
// d has changed
Chain<int> *C1 = new Chain<T>;
Chain<int> *C2 = new Chain<T>;
// define an array to record vertices that are in C2
bool *InC2 = new bool[n+1];
// initialize p[1:n] = 0 and InC2[1:n] = false
for (int i = 0; i <= n; i++) {
p[i] = 0;
InC2[i] = false;
}
p[s] = s;
// initialize d[] = d^1() and C1
// C1 will contain all vertices adjacent from s
int u;
T c; // edge cost
First(s, u, c);
while (u) {
d[u] = c;
p[u] = s;
C1->Insert(0,u); // insert at front
Next(s, u, c);
}
d[s] = 0;
// do n - 2 rounds of updating d
for (int k = 2; k < n; k++) {
if (C1->IsEmpty()) break; // no more changes possible
// process vertices on C1
while (!C1->IsEmpty()) {
// get a vertex u whose d value has changed
C1->Delete(1,u); // delete from front
// update d for the neighbors v of u
int v;
First(u, v, c);
while (v) {
if (!p[v] || d[u] + c < d[v]) {
// this is either the first path to v
// or is a shorter path than earlier ones
d[v] = d[u] + c;
p[v] = u;
if (!InC2[v]) {
C2->Insert(0,v);
InC2[v] = true;
}
}
Next(u, v, c);
}
}
// swap C1 and C2 for next update round
Swap(C1, C2);
// reset InC2[1:n] to false
ChainIterator<int> c2;
int *w = c2.Initialize(*C1);
while (w) {
InC2[*w] = false;
w = c2.Next();
}
}
delete C1;
delete C2;
DeactivatePos();
}
Complexity Analysis
Since the number of vertices on C1
and
C2 is O(e),
each iteration of the for (int k ...
loop takes
O(n2) time when the adjacency-matrix
representation is used and
O(e) time when the linked-list representation
is used. Therefore, the overall complexity is
O(n3) when the adjacency-matrix
representation is used and
O(ne) when the linked-list representation
is used.
Difference Equations
An important application of the Bellman-Ford algorithm is in the
solution of a system of difference equations.
Such a system is comprised of m equations
of the form
xi-xj <= cq
where
xi and xj are
variables and cq is a constant.
A 4 variable system with 6 equations is given below.
x1 - x2 <= -2
x1 - x3 <= -6
x3 - x4 <= 5
x3 - x2 <= 4
x2 - x4 <= 1
x4 - x1 <= 1
One application of a system of difference equations is in event
scheduling. Suppose we are to schedule four related events such that
event 2 takes place at least two days after event 1;
event 3 takes place at least 6 days after event 1;
event 4 takes place no more than 5 days before event 3;
event 2 takes place no more than 4 days before event 3;
event 4 takes place no more than 1 day before event 2;
and
event 1 takes place no more than 1 day before event 4.
If we let xi denote the time
when event i takes place, then the
xis must satisfy the
4 variable system with 6 equations given above.
In fact, any assignement of nonnegative integers to
the
xis that satisfies all
6 equations defines a valid schedule for the 4 events.
We can transform any system of difference equations into a weighted
digraph as follows.
-
The weighted digraph has one vertex for each variable
plus an additional vertex.
-
There is a directed edge from this additional
vertex to each of the remaining vertices.
The weight of each of these edges is zero.
-
For each equation
xi - xj <= cq,
there is a directed edge from the vertex that represents
xj
to the one that represents
xi
The weight of this edge is
cq.
We shall show shortly that the constructed digraph has a
negative-length cycle iff the system of difference equations it
is constructed from has no solution. Further, the lengths of the shortest
paths from the additional vertex to the remaining vertex define
a feasible solution when the digraph has no cycle of negative length.
For our 4 variable 6 equation example, we let vertex i
represent variable
xi, 1 <= i <= 4
and we let vertex 5 be the additional vertex. The directed edges and their
costs are {(5,1,0), (5,2,0), (5,3,0), (5,4,0), (2,1,-2), (3,1,-6),
(4,3,5), (2,3,4), (4,2,1), (1,4,1)}, where the triple (u,v,c)
denotes the directed edge from vertex u to
vertex v whose cost is c.
Running the Bellman-Ford algorithm on the constructed digraph
the lengths of the shortest paths from vertex 5 to vertices 1, 2, 3, and 4
are determined to be -6, -4, 0, and -5.
The assignment x1 = -6,
x2 = -4,
x3 = 0, and
x4 = -5 satisfies the 6 equations of our
difference system. From this assignment, we may obtain other assignments by
adding any constant to all
xis.
For example, by adding 6, we get the assignment
x1 = 0,
x2 = 2,
x3 = 6, and
x4 = 3. Since all times are now nonnegative,
we can use this assignment as a schedule for the 4 events.
Theorem
-
The constructed digraph has a negative-length cycle iff the
system of difference equations it is constructed from has no solution.
-
If the system has a solution, then the lengths of the shortest paths from
the additional vertex to the remaining vertices defines a solution.
Proof
-
Suppose that the constructed digraph has a negative-length cycle
C.
Without loss of generality, we may assume that this cycle is 1, 2, 3, ...,
u, 1 where vertex i represents
variable xi, 1 <= i
<= u. Note that the additional vertex is on no
cycle because it has no incoming edge.
The edge (i,i+1) represents the equation
xi+1 - xi <= cost(i,i+1),
and the edge
(u,1) represents the equation
x1 - xu <= cost(u,1).
Adding up both sides of the equations represented by the
edges on the negative-length cycle C,
we see that all variables cancel out on the left side to yield a left-side
sum of zero. The sum of the right sides is the sum of the edge costs
on the cycle. Since this sum is negative, adding the equations yields the
equation
0 <= sum of edge costs < 0
which is a false statement. Therefore, no assignment to the
xis can satisfy the given equations.
The fact that if the system has no solution then the digraph has a
negative-length cycle is established in a similar way.
-
We may assume the digraph has no negative-length cycle.
Consequently, there is a shortest path from the additional vertex
to each of the remaining vertices. Let d(i)
be the length of the shortest path from the additional vertex to the vertex
i that represents variable xi.
Let
xi-xj <= cq
be any equation in the given system.
From the definition of d() it follows that
d(i) <= d(j) + cost(j,i) = d(j) + cq.
Therefore,
d(i) - d(j) <= cq.
Hence setting xi = d(i)
for all i results in an assignment that
satisfies all equations.