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]\)?