We will now consider three variations of the single-source shortest-path problem:
Negative weights:

Directed graphs:
DAG's:
Algorithm: DAG-SPT (G, s)
Input: Graph G=(V,E) with edge weights and designated source vertex s.
     // Initialize priorities and create empty SPT. 
1.   Set priority[i] = infinity for each vertex ;
     // Sort vertices in topological order and place in list. 
2.   vertexList = topological sort of vertices in G;
     // Place source in shortest path tree. 
3.   priority[s] = 0
4.   Add s to SPT;
     // Process remaining vertices. 
5.   while vertexList.notEmpty()
         // Extract next vertex in topological order. 
6.       v = extract next vertex in vertexList;
         // Explore edges from v. 
7.       for each neighbor u of v
8.           w = weight of edge (v, u);
             // If there's a better way to get to u (via v), then update. 
9.           if priority[u] > priority[v] + w
10.              priority[u] = priority[v] + w
11.              predecessor[u] = v
12.          endif
13.      endfor
14.  endwhile
15.  Build SPT;
16.  return SPT
Output: Shortest Path Tree (SPT) rooted at s.
  
  



Algorithm: maxWeightPath (G, s)
Input: Graph G=(V,E) with edge weights and designated source vertex s.
     // ... initialization same as DAG-SPT ... 
5.   while vertexList.notEmpty()
           // ... same as DAG-SPT ... 
           // Notice the reversal from ">" to "<": 
9.         if priority[u] < priority[v] + w
10.            priority[u] = priority[v] + w
               // ...  
12.        endif
14.  endwhile
     // ... same as DAG-SPT ... 
Output: Longest Path from source s.
       
       
The shortest-path between every pair of vertices:
Algorithm: Dijkstra-AllPairsShortestPaths (G)
Input: Graph G with edge weights.
1.   for each vertex i in G
       // Find the shortest path tree with i as source. 
2.     Dijkstra-SPT (i)
3.   endfor
4.   Construct paths;
5.   return paths
Output: shortest-path between each pair of vertices.
  
    Key ideas in the Floyd-Warshall algorithm:





| Dijk | = | wij min ( Dijk-1, Dikk-1 + Dkjk-1 ) | if k = -1 if k >= 0 | 
Note:
 In-Class Exercise 9.1:
Write pseudocode to recursively compute Dijn-1.
Start with FloydWarshallRec.java and:
  
  
Implementation:
Algorithm: floydWarshall (adjMatrix)
Input: Adjacency matrix representation: adjMatrix[i][j] = weight of
       edge (i,j), if an edge exists; adjMatrix[i][j]=0 otherwise.
      // Initialize the "base case" corresponding to k == -1. 
      // Note: we set the value to "infinity" when no edge exists. 
      // If we didn't, we would have to include a test in the main loop below. 
1.    for each i, j
2.        if adjMatrix[i][j] > 0
3.            D-1[i][j] = adjMatrix[i][j]
4.        else
5.            D-1[i][j] = infinity
6.        endif
7.    endfor
      // Start iterating over k. At each step, use the previously computed matrix. 
8.    for k=0 to numVertices-1
          // Compute Dk[i][j] for each i,j.
9.        for i=0 to numVertices-1
10.           for j=0 to numVertices-1
11.               if i != j 
                  // Use the relation between Dk and Dk-1 
12.                   if Dk-1[i][j] < Dk-1[i][k] + Dk-1[k][j]     // CASE 2
13.                       Dk[i][j] = Dk-1[i][j]
14.                   else
15.                       Dk[i][j] = Dk-1[i][k] + Dk-1[k][j]      // CASE 3
16.                   endif
17.               endif
18.           endfor
19.       endfor
        // Matrix copy: current Dk becomes next iteration's Dk-1 
20.     Dk-1 = Dk
21.   endfor
      // The Dk matrix only provides optimal costs. The 
      // paths still have to be built using Dk. 
22.   Build paths;
23.   return paths
Output: paths[i][j] = the shortest path from i to j.
  
   
  public void allPairsShortestPaths (double[][] adjMatrix)
  {
    // Dk_minus_one = weights when k = -1 
    for (int i=0; i < numVertices; i++) {
      for (int j=0; j < numVertices; j++) {
        if (adjMatrix[i][j] > 0)
          Dk_minus_one[i][j] = adjMatrix[i][j];
        else 
          Dk_minus_one[i][j] = Double.MAX_VALUE;
        // NOTE: we have set the value to infinity and will exploit 
        // this to avoid a comparison. 
      }
    }
    
    // Now iterate over k. 
    for (int k=0; k < numVertices; k++) {
      // Compute Dk[i][j], for each i,j 
      for (int i=0; i < numVertices; i++) {
        for (int j=0; j < numVertices; j++) {
          if (i != j) {
            // D_k[i][j] = min ( D_k-1[i][j], D_k-1[i][k] + D_k-1[k][j]. 
            if (Dk_minus_one[i][j] < Dk_minus_one[i][k] + Dk_minus_one[k][j])
              Dk[i][j] = Dk_minus_one[i][j];
            else 
              Dk[i][j] = Dk_minus_one[i][k] + Dk_minus_one[k][j];
          }
        }
      }
      // Now store current Dk into D_k-1 
      for (int i=0; i < numVertices; i++) {
        for (int j=0; j < numVertices; j++) {
          Dk_minus_one[i][j] = Dk[i][j];
        }
      }
    } // end-outermost-for 
    // Next, build the paths by doing this once for each source. 
    // ... (not shown) ... 
  }
  
  Analysis:
In-Class Exercise 9.2: In FloydWarshall.java (the sample code above), count the number of times the innermost if-statement is executed. Explain the difference between this count and the one from the previous exercise: why is the earlier one so much higher?
An optimization:
12.           if Dk-1[i][j] < Dk-1[i][k] + Dk-1[k][j]
13.               Dk[i][j] = Dk-1[i][j]
14.           else
15.               Dk[i][j] = Dk-1[i][k] + Dk-1[k][j]
16.           endif
    
  with 
    
              // The first Dk[i][j] is really Dk-1[i][j] 
              // because we haven't written into it yet. 
12.           if Dk[i][j] < Dk[i][k] + Dk[k][j]
                  // This is superfluous: 
13.               Dk[i][j] = Dk[i][j]
14.           else
                  // This is all we need: 
15.               Dk[i][j] = Dk[i][k] + Dk[k][j]
16.           endif
    
  
Algorithm: floydWarshallOpt (adjMatrix)
Input: Adjacency matrix representation: adjMatrix[i][j] = weight of
       edge (i,j), if an edge exists; adjMatrix[i][j]=0 otherwise.
      // ... initialization similar to that in floydWarshall ... 
1.    for k=0 to numVertices-1
2.        for i=0 to numVertices-1
3.            for j=0 to numVertices-1
4.                if i != j 
                      // Use the same matrix. 
5.                    if D[i][k] + D[k][j] < D[i][j] 
6.                        D[i][j] = D[i][k] + D[k][j]
7.                    endif
8.                endif
9.            endfor
10.       endfor
11.   endfor
      // ... path construction ... 
  
  
Consider an alternative iterative version of Floyd-Warshall:
    for k=0 to numVertices-1
        for each i,j
            Compute Dkij
        endfor
    endfor
  
  
    for each i,j
        for k=0 to numVertices-1
            Compute Dkij
        endfor
    endfor
  
  Would this work?
      
    while changeOccurred
        changeOccurred = false
        for each i,j
            for k=0 to numVertices-1
                // Compute Dkij
                if D[i][k] + D[k][j] < D[i][j] 
                    // Improved shortest-cost. 
                    D[i][j] = D[i][k] + D[k][j]
                    // Since this may propagate, we have to continue iteration. 
                    changeOccurred = true
                endif
            endfor
        endfor
    endwhile 
  
  
Algorithm: floydWarshallIterative (adjMatrix)
Input: Adjacency matrix representation: adjMatrix[i][j] = weight of
       edge (i,j), if an edge exists; adjMatrix[i][j]=0 otherwise.
      // ... initialization similar to that in floydWarshallOpt ... 
1.    changeOccurred = true
2.    while changeOccurred
          changeOccurred = false
3.        for i=0 to numVertices-1
4.            for j=0 to numVertices-1
5.                if i != j 
                      // "k" is now in the innermost loop. 
6.                    for k=0 to numVertices
7.                        if D[i][k] + D[k][j] < D[i][j] 
                              // Improved shortest-cost. 
8.                            D[i][j] = D[i][k] + D[k][j]
                              // Since this may propagate, we have to continue iteration. 
9.                            changeOccurred = true
10.                       endif
11.                   endfor
10.               endif
11.           endfor
12.       endfor
13.   endwhile
      // ... path construction ... 
  
  What do we mean by "distributed routing"?
In-Class Exercise 9.3: Why aren't routes computed just once and for all whenever a network is initialized?
Distributed Floyd-Warshall: a purely distibuted algorithm
    while changeOccurred
        changeOccurred = false
        for i=0 to numVertices-1
            for j=0 to numVertices-1
            // Node i says "let me try to get to destination j via k". 
            for k=0 to numVertices
                // If it's cheaper for me to go via k, let me record that. 
                if D[i][k] + D[k][j] < D[i][j] 
                    // Improved shortest-cost: my cost to neighbor k, plus k's cost to j 
                    D[i][j] = D[i][k] + D[k][j]
                    changeOccurred = true
                endif
            endfor
         endfor
   endwhile
   
  





An alternative: running Dijkstra at each node
The process of "routing":
| Destination | Current cost | Outgoing link | 
| ... | ... | ... | 
| ... | ... | ... | 
| 5 | 4 | (0,2) | 
| ... | ... | ... | 
| Source | Destination | Current cost | Outgoing link | 
| ... | ... | ... | ... | 
| ... | ... | ... | ... | 
| 1 | 5 | x | (0,2) | 
| ... | ... | ... | ... | 
| 0 | 5 | y | (0,3) | 
| ... | ... | ... | ... | 
Internet routing:
What is dynamic programming?
Consider the following routing problem:

Let's now apply dynamic programming:




Algorithm: computeOptimal 1. computeD(0,0) Method: computeD(i,j) 1. if (i,j) is target // if bottom-right 2. return 0 3. endif 4. rightcost = xi,j + computeD(i,j+1) 5. belowcost = yi,j + computeD(i+1,j) 6. diagonalcost = zi,j + computeD(i+1,j+1) 7. return min(rightcost, belowcost, diagonalcost)
Algorithm: computeOptimal 1. initialize 2D array D[i,j] = -1 2. computeD(0,0) Method: computeD(i,j) 1. if D[i,j] ≥ 0 // Already computed 2. return D[i,j] 3. else if (i,j) is target // if bottom-right 4. D[i,j] = 0 5. return 0 6. endif 7. rightcost = xi,j + computeD(i,j+1) 8. belowcost = yi,j + computeD(i+1,j) 9. diagonalcost = zi,j + computeD(i+1,j+1) 10. return min(rightcost, belowcost, diagonalcost)
Algorithm: computeOptimal 1. D[lastrow][lastcolumn] = 0 2. for i = lastrow downto 1 3. for j = lastcolumn downto 1 4. rightcost = xi,j + D[i][j+1] 5. belowcost = yi,j + D[i+1][j] 6. diagonalcost = zi,j + D[i+1][j+1] 7. D[i][j] = min(rightcost, belowcost, diagonalcost) 8. endfor 9. endfor 10. return D[0][0]Note:

In-Class Exercise 9.4: Calculate the remaining D[i][j] values.
Finally, what about the route itself?

In-Class Exercise 9.5: Modify the pseudocode (iterative version) to record the outgoing edge information.
Let's review Floyd-Warshall in terms of dynamic programming:
| Dijk | = | wij min ( Dijk-1, Dikk-1 + Dkjk-1 ) | if k = -1 if k >= 0 | 
Consider the following problem: (Contiguous Load Balancing)

In-Class Exercise 9.6: Write pseudocode to take as input (1) the task times, and (2) the number of processors, and produce a (contiguous) partition of tasks among the processors.
Example: dynamic programming applied to the load balancing problem
(where j ranges over { 0, ..., i }).

| Optimal solution to problem of size (k, i) | = | Combination of (maximum of) | optimal solution to problem of size (k-1, j) (for some j) | and | some computation (sum across last partition) | 
In terms of the equation:
| Dik | = | max | (Dj*k-1, | sj*+1 + ... + si) | 
Implementation:
Algorithm: dynamicProgrammingLoadBalancing (numTasks, taskTimes, numProcessors)
Input: the number of tasks (numTasks), the number of processors (numProcessors),
       taskTimes[i] = time required for task i.
      // Initialization. First, the base cases: 
1.    D[1][i] = sum of taskTimes[0], ... ,taskTimes[i];
      // We will set the other values to infinity and exploit this fact in the code. 
2.    D[k][i] = infinity, for all i and k > 1
      // Now iterate over the number of processors. 
3.    for k=2 to numProcessors
          // Optimally allocate i tasks to k processors. 
4.        for i=0 to numTasks-1
              // Find the optimal value of D[k][i] using prior computed values. 
5.            min = max = infinity
              // Try each value of j in the recurrence relation. 
6.            for j=0 to i
                  // Compute sj+1 + ... + si 
7.                sum = 0
8.                for m=j+1 to i
9.                    sum = sum + taskTimes[m]
10.               endfor
                  // Use the recurrence relation. 
11.               max = maximum (D[k-1][j], sum)
                  // Record the best (over j). 
12.               if max < min
13.                   D[k][i] = max
14.                   min = max
15.               endif
16.           endfor // for j=0 ... 
          // Optimal D[k][i] found. 
17.       endfor // for i=0 ... 
18.   endfor // outermost: for k=2 ... 
18.   Find the actual partition;
19.   return partition
Output: the optimal partition
  
  
Analysis:
An optimization:
Algorithm: dynamicProgrammingLoadBalancing (numTasks, taskTimes, numProcessors)
Input: the number of tasks (numTasks), the number of processors (numProcessors),
       taskTimes[i] = time required for task i.
      // Precompute partial sums 
      for i=0 to numTasks
          partialSum[i] = 0
          for j=0 to i
              partialSum = partialSum + taskTimes[i]
          endfor
      endfor
      // ... Remaining initialization as before ... 
      for k=2 to numProcessors
          for i=0 to numTasks-1
              for j=0 to i
                  // Note: sj+1 + ... + si = partialSum[i]-partialSum[j] 
                  // Use the recurrence relation. 
                  max = maximum (D[k-1][j], partialSum[i] - partialSum[j])
                  // ... remaining code is identical ... 
      
  
Consider the following problem:
    x0 , x1 , ..., xn-1
  
  find the contiguous subsequence 
  xi , ..., xjwhose sum is the largest.

In-Class Exercise 9.7: Implement the naive and most straightforward approach: try all possible contiguous subsequences. Write down pseudocode and then analyse the running time.
Using dynamic programming:
Largest suffix (of a prefix):
    x0 , x1 , ..., xn-1
  
 find, for each k, the largest-sum suffix of the numbers
  
    x0 , x1 , ..., xk
  
 where the sum is taken to be zero, if negative.

Dynamic programming algorithm for suffix problem:
    x0 , x1 , ..., xk
  
| Dk | = | Dk-1 + xk 0 | if Dk-1 + xk > 0 otherwise | 
    for k=1 to n-1
      // Apply the dynamic programming equation 
        if Dk-1 + xk > 0
            Dk = Dk-1 + xk
        else
            Dk = 0
        endif
    endfor
    
| D0 | = | x0 0 | if x0 > 0 otherwise | 
Dynamic programming algorithm for subsequence problem:
    x0 , x1 , ..., xk
  
| Sk | = | Dk-1 + xk Sk-1 | if Dk-1 + xk > Sk-1 otherwise | 
Algorithm: maxSubsequenceSum (X)
Input: an array of numbers, at least one of which is positive
      // Initial value of D   
  1.  if X[0] > 0
  2.      D = X[0]
  3.  else
  4.      D = 0
  5.  endif
      // Initial value of S, the current best max   
  6.  S = X[0]
      // Single scan   
  7.  for k = 1 to n-1
          // Update S   
  8.      if D + X[k] > S
  9.          S = D + X[k]
  10.     endif
          // Update D   
  11.     if D + X[k] > 0
  12.         D = D + X[k]
  13.     else
  14    .     D = 0
  15.     endif
  16. endfor
  17. return S
      
Consider the following problem:
| Key | Access probability | 
| A | 0.4 | 
| B | 0.1 | 
| C | 0.2 | 
| D | 0.1 | 
| E | 0.2 | 
Past solutions we have seen:
 In-Class Exercise 9.8:
Consider the following two optimal-list algorithms:
 
 
Analyze the running time of each algorithm in terms of the
number of list elements n.
Algorithm: optimalList (E, p)
Input: set of elements E, with probabilities p
      // Base case: single element list 
1.    if |E| = 1
2.        return list containing E
3.    endif
4.    m = argmaxi p(i)      // Which i gives the max
5.    front = new node with m
6.    list = optimalList (E - {m}, p - pm)
7.    return list.addToFront (front)
    
 
Algorithm: optimalList2 (E, p)
Input: set of elements E, with probabilities p
      // Base case: single element list 
1.    if |E| = 1
2.        return list containing E
3.    endif
4.    for i=0 to E.length
5.        front = new node with i
6.        list = optimalList2 (E - {i}, p)
7.        tempList = list.addToFront (front)
8.        cost = evaluate (tempList)
9.        if cost < bestCost
10.           best = i
11.           bestList = tempList
12.       endif
13.   endfor
14.   return bestList
    
 
Optimal binary search tree:

In-Class Exercise 9.9: Which of the two list algorithms could work for the optimal binary tree problem? Write down pseudocode.
Dynamic programming solution:

Implementation:
    // Initialize C and apply base cases. 
    for i=0 to numKeys-2
        for j=i+1 to numKeys-1
            min = infinity
            sum = pi + ... + pj;
            for k=i to j
                if C(i, k-1) + C(k+1, j) + sum < min
                    min = C(i, k-1) + C(k+1, j) + sum
          ...
    
    Suppose, above, i=0, j=10 and k=1 in the innermost loop
       
Algorithm: optimalBinarySearchTree (keys, probs)
Input: keys[i] = i-th key,  probs[i] = access probability for i=th key.
      // Initialize array C, assuming real costs are positive (or zero). 
      // We will exploit this entry to check whether a cost has been computed. 
1.    for each i,j set C[i][j] = -1;
      // Base cases: 
2.    for each i, C[i][i] = probs[i];
      // Search across various i, j ranges. 
3.    for i=0 to numKeys-2
4.        for j=i+1 to numKeys-1
              // Recursive method computeC actually implements the recurrence. 
5.            C[i][j] = computeC (i, j, probs)
6.        endfor
7.    endfor
      // At this point, the optimal solution is C(0, numKeys-1) 
8.    Build tree;
9.    return tree
Output: optimal binary search tree
    
    
Algorithm: computeC (i, j, probs)
Input: range limits i and j, access probabilities
     // Check whether sub-problem has been solved before. 
     // If so, return the optimal cost. This is an O(1) computation. 
1.   if (C[i][j] >= 0)
2.       return C[i][j]
3.   endif
     // The sum of access probabilities used in the recurrence relation. 
4.   sum = probs[i] + ... + probs[j];
     // Now search possible roots of the tree. 
5.   min = infinity
6.   for k=i to j
         // Optimal cost of the left subtree (for this value of k). 
7.       Cleft = computeC (i, k-1)
         // Optimal cost of the right subtree. 
8.       Cright = computeC (k+1, j)
         // Record optimal solution. 
9.       if Cleft + Cright < min
10.          min = Cleft + Cright
11.      endif
12.  endfor
13.  return min
Output: the optimal cost of a binary tree for the sub-range keys[i], ..., keys[j].
    
    Analysis:
So, what to make of dynamic programming and its "strangeness"?
Key points to remember:
 In-Class Exercise 9.10:
For the moment, let's set aside efficiency and merely get some
practice with seeing if dynamic programming can be set up properly.
Consider an array A with n elements and the following
three problems:
For each of the above problems, can 
\(D_{ij}\) be written
in terms of \(D_{ik}\) and \(D_{kj}\) where \(i\leq k\leq j\),
with possibly a few additional terms like \(A[k]\)?