13.10 Specialised algorithms on weighted graphs
In this section we present two specialised algorithms on weighted directed graphs:
- Floyd’s algorithm
- This algorithm finds the shortest paths between all pairs of vertices in a graph.
- Topological sort
- This algorithm sorts the vertices of a directed acyclic graph (DAG), so that no edges point “backwards”. This is crucial to solve various scheduling problems.
13.10.1 All-pairs shortest paths: Floyd’s algorithm
We next consider the problem of finding the shortest distance between all pairs of vertices in the graph, called the all-pairs shortest paths problem. To be precise, for every , calculate .
One solution is to run Dijkstra’s
algorithm
times, each time computing the shortest path from a different start
vertex. If
is sparse (that is,
)
then this is a good solution, because the total cost will be
for the version of Dijkstra’s algorithm based on priority queues. For a
dense graph, the priority queue version of Dijkstra’s algorithm yields a
cost of
,
but the version using minVertex
yields a cost of
.
Another solution that limits processing time to regardless of the number of edges is known as Floyd’s algorithm. It is an example of dynamic programming. The chief problem with solving this problem is organising the search process so that we do not repeatedly solve the same subproblems. We will do this organisation through the use of the -path. Define a k-path from vertex to vertex to be any path whose intermediate vertices (aside from and ) all have indices less than . A 0-path is defined to be a direct edge from to . Figure 13.13 illustrates the concept of -paths.

Define
to be the length of the shortest
-path
from vertex
to vertex
.
Assume that we already know the shortest
-path
from
to
.
The shortest
-path
either goes through vertex
or it does not. If it does go through
,
then the best path is the best
-path
from
to
followed by the best
-path
from
to
.
Otherwise, we should keep the best
-path
seen before. Floyd’s algorithm simply checks all of the possibilities in
a triple loop. Here is the implementation for Floyd’s algorithm. At the
end of the algorithm, array D
stores the all-pairs shortest
distances.
function floyd(graph):
= new Map() of vertex pairs to distances
distances for each v in graph.vertices():
for each w in graph.vertices():
put((v, w), ∞)
distances.put((v, v), 0)
distances.for each e in G.outgoingEdges(v):
put((v, e.end), e.weight)
distances.
// Compute all k-paths
for each k in graph.vertices():
for each v in graph.vertices():
for each w in graph.vertices():
= distances.get(v, k) + distances.get(k, w)
dist if dist < distances.get(v, w):
put((v, w), dist)
distances.return distances
Clearly this algorithm requires running time, and it is the best choice for dense graphs because it is (relatively) fast and easy to implement.
13.10.2 Acyclic graphs: Topological sort
Assume that we need to schedule a series of tasks, such as classes or construction jobs, where we cannot start one task until after its prerequisites are completed. We wish to organise the tasks into a linear order that allows us to complete them one at a time without violating any prerequisites. We can model the problem using a DAG. The graph is directed because one task is a prerequisite of another – the vertices have a directed relationship. It is acyclic because a cycle would indicate a conflicting series of prerequisites that could not be completed without violating at least one prerequisite. The process of laying out the vertices of a DAG in a linear order to meet the prerequisite rules is called a topological sort.
Figure 13.14 illustrates the problem. An acceptable topological sort for this example is J1, J2, J3, J4, J5, J6, J7. However, other orders are also acceptable, such as J1, J3, J2, J6, J4, J5, J7.

Depth-first algorithm
A topological sort may be found by performing a depth-first search (DFS) on the graph. When a vertex is visited, no action is taken (i.e., function preVisit from Section 13.4.1 does nothing). When the recursion pops back to that vertex, function postVisit adds the vertex to a stack. In the end, the stack is returned to the caller.
The reason that we use a stack is that this algorithm produces the vertices in reverse topological order. And if we pop the elements in the stack, they will be popped in topological order.
So the DFS algorithm yields a topological sort in reverse order. It does not matter where the sort starts, as long as all vertices are visited in the end. Here is implementation for the DFS-based algorithm.
function topsortDFS(graph):
= new Set()
visited = new Stack()
sortedVertices for each v in graph.vertices():
if v not in visited:
topsortHelperDFS(graph, v, sortedVertices, visited)return sortedVertices
function topsortHelperDFS(graph, v, sortedVertices, visited):
if v not in visited:
add(v)
visited.for each e in graph.outgoingEdges(v):
topsortHelperDFS(graph, e.end, sortedVertices, visited)push(v) // postVisit sortedVertices.
If we use this algorithm starting at vertex J1 in Figure 13.14 and visit adjacent neighbours in alphabetic order, the vertices of the graph are pushed to the stack in the order J7, J5, J4, J6, J2, J3, J1. If we now pop them one by one we get the topological sort J1, J3, J2, J6, J4, J5, J7.
Here is another example.
Queue-based algorithm
We can also implement topological sort using a queue instead of recursion, as follows.
First visit all edges, counting the number of edges that lead to each vertex (i.e., count the number of prerequisites for each vertex). All vertices with no prerequisites are placed on the queue. We then begin processing the queue. When vertex is taken off of the queue, it is added to a list containing the topological order, and all neighbours of (that is, all vertices that have as a prerequisite) have their counts decremented by one. Place on the queue any neighbour whose count becomes zero. If the queue becomes empty without having added all vertices to the final list, then the graph contains a cycle (i.e., there is no possible ordering for the tasks that does not violate some prerequisite). The order in which the vertices are added to the final list is the correct one, so if traverse the final list we will get the elements in topological order.
Applying the queue version of topological sort to the graph of Figure 13.14 produces J1, J2, J3, J6, J4, J5, J7. Here is an implementation of the algorithm.
function topsortBFS(graph):
// Initialise the prerequisite counts
= new Map() of vertices to prerequisite count
counts for each v in graph.vertices():
put(v, 0)
counts.for each v in graph.vertices():
for each edge in graph.outgoingEdges(v):
// Increase v's prerequisite count
= counts.get(edge.end) + 1
newCount put(edge.end, newCount)
counts.
// Initialise the queue
= new Queue()
queue for each v in graph.vertices():
// Only add vertices that have no prerequisites
if counts.get(v) == 0:
enqueue(v)
queue.
// Process the vertices
= new Queue()
sortedVertices while not queue.isEmpty():
= queue.dequeue()
v enqueue(v) // preVisit
sortedVertices.for each e in graph.outgoingEdges(v):
// Decrease v's prerequisite count
= counts.get(edge.end) - 1
newCount put(edge.end, newCount)
counts.if newCount == 0:
enqueue(e.end)
queue.return sortedVertices
Here is an illustration of the queue-based algorithm.