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.
  1. The weighted digraph has one vertex for each variable plus an additional vertex.
  2. There is a directed edge from this additional vertex to each of the remaining vertices. The weight of each of these edges is zero.
  3. 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
  1. The constructed digraph has a negative-length cycle iff the system of difference equations it is constructed from has no solution.
  2. 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
  1. 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.
  2. 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.