Skip to content

Commit 10f2934

Browse files
williamfisetclaude
andauthored
Refactor TopologicalSortAdjacencyList: add docs, clean up (#1308)
* Refactor TopologicalSortAdjacencyList: add docs, clean up Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Simplify dagShortestPath: flatten nesting, collapse null/min check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6ca34b3 commit 10f2934

File tree

1 file changed

+63
-74
lines changed

1 file changed

+63
-74
lines changed
Lines changed: 63 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
/**
2-
* This topological sort implementation takes an adjacency list of an acyclic graph and returns an
3-
* array with the indexes of the nodes in a (non unique) topological order which tells you how to
4-
* process the nodes in the graph. More precisely from wiki: A topological ordering is a linear
5-
* ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes
6-
* before v in the ordering.
2+
* Topological sort implementation using DFS on an adjacency list of a Directed Acyclic Graph (DAG).
3+
* Returns an array with the node indexes in a (non-unique) topological order: for every directed
4+
* edge u -> v, u comes before v in the ordering.
75
*
8-
* <p>Time Complexity: O(V + E)
6+
* <p>Also includes a method to find shortest paths in a DAG using the topological ordering.
7+
*
8+
* <p>Time: O(V + E)
9+
*
10+
* <p>Space: O(V + E)
911
*
1012
* @author William Fiset, william.alexandre.fiset@gmail.com
1113
*/
@@ -15,95 +17,90 @@
1517

1618
public class TopologicalSortAdjacencyList {
1719

18-
// Helper Edge class to describe edges in the graph
1920
static class Edge {
2021
int from, to, weight;
2122

22-
public Edge(int f, int t, int w) {
23-
from = f;
24-
to = t;
25-
weight = w;
23+
public Edge(int from, int to, int weight) {
24+
this.from = from;
25+
this.to = to;
26+
this.weight = weight;
2627
}
2728
}
2829

29-
// Helper method that performs a depth first search on the graph to give
30-
// us the topological ordering we want. Instead of maintaining a stack
31-
// of the nodes we see we simply place them inside the ordering array
32-
// in reverse order for simplicity.
33-
private static int dfs(
34-
int i, int at, boolean[] visited, int[] ordering, Map<Integer, List<Edge>> graph) {
35-
36-
visited[at] = true;
37-
38-
List<Edge> edges = graph.get(at);
39-
40-
if (edges != null)
41-
for (Edge edge : edges) if (!visited[edge.to]) i = dfs(i, edge.to, visited, ordering, graph);
42-
43-
ordering[i] = at;
44-
return i - 1;
45-
}
46-
47-
// Finds a topological ordering of the nodes in a Directed Acyclic Graph (DAG)
48-
// The input to this function is an adjacency list for a graph and the number
49-
// of nodes in the graph.
50-
//
51-
// NOTE: 'numNodes' is not necessarily the number of nodes currently present
52-
// in the adjacency list since you can have singleton nodes with no edges which
53-
// wouldn't be present in the adjacency list but are still part of the graph!
54-
//
30+
/**
31+
* Finds a topological ordering of the nodes in a DAG.
32+
*
33+
* <p>NOTE: {@code numNodes} is not necessarily the number of nodes in the adjacency list since
34+
* singleton nodes with no edges wouldn't be present but are still part of the graph.
35+
*
36+
* @param graph adjacency list mapping node index to outgoing edges.
37+
* @param numNodes total number of nodes in the graph.
38+
* @return array of node indexes in topological order.
39+
*/
5540
public static int[] topologicalSort(Map<Integer, List<Edge>> graph, int numNodes) {
56-
5741
int[] ordering = new int[numNodes];
5842
boolean[] visited = new boolean[numNodes];
5943

6044
int i = numNodes - 1;
6145
for (int at = 0; at < numNodes; at++)
62-
if (!visited[at]) i = dfs(i, at, visited, ordering, graph);
46+
if (!visited[at])
47+
i = dfs(i, at, visited, ordering, graph);
6348

6449
return ordering;
6550
}
6651

67-
// A useful application of the topological sort is to find the shortest path
68-
// between two nodes in a Directed Acyclic Graph (DAG). Given an adjacency list
69-
// this method finds the shortest path to all nodes starting at 'start'
70-
//
71-
// NOTE: 'numNodes' is not necessarily the number of nodes currently present
72-
// in the adjacency list since you can have singleton nodes with no edges which
73-
// wouldn't be present in the adjacency list but are still part of the graph!
74-
//
75-
public static Integer[] dagShortestPath(Map<Integer, List<Edge>> graph, int start, int numNodes) {
52+
// Places nodes into the ordering array in reverse DFS post-order.
53+
private static int dfs(
54+
int i, int at, boolean[] visited, int[] ordering, Map<Integer, List<Edge>> graph) {
55+
visited[at] = true;
56+
57+
List<Edge> edges = graph.get(at);
58+
if (edges != null)
59+
for (Edge edge : edges)
60+
if (!visited[edge.to])
61+
i = dfs(i, edge.to, visited, ordering, graph);
62+
63+
ordering[i] = at;
64+
return i - 1;
65+
}
7666

67+
/**
68+
* Finds the shortest path from {@code start} to all other reachable nodes in a DAG.
69+
*
70+
* <p>NOTE: {@code numNodes} is not necessarily the number of nodes in the adjacency list since
71+
* singleton nodes with no edges wouldn't be present but are still part of the graph.
72+
*
73+
* @param graph adjacency list mapping node index to outgoing edges.
74+
* @param start the source node.
75+
* @param numNodes total number of nodes in the graph.
76+
* @return array of shortest distances, null for unreachable nodes.
77+
*/
78+
public static Integer[] dagShortestPath(
79+
Map<Integer, List<Edge>> graph, int start, int numNodes) {
7780
int[] topsort = topologicalSort(graph, numNodes);
7881
Integer[] dist = new Integer[numNodes];
7982
dist[start] = 0;
8083

8184
for (int i = 0; i < numNodes; i++) {
82-
8385
int nodeIndex = topsort[i];
84-
if (dist[nodeIndex] != null) {
85-
List<Edge> adjacentEdges = graph.get(nodeIndex);
86-
if (adjacentEdges != null) {
87-
for (Edge edge : adjacentEdges) {
88-
89-
int newDist = dist[nodeIndex] + edge.weight;
90-
if (dist[edge.to] == null) dist[edge.to] = newDist;
91-
else dist[edge.to] = Math.min(dist[edge.to], newDist);
92-
}
93-
}
86+
if (dist[nodeIndex] == null)
87+
continue;
88+
for (Edge edge : graph.getOrDefault(nodeIndex, Collections.emptyList())) {
89+
int newDist = dist[nodeIndex] + edge.weight;
90+
if (dist[edge.to] == null || newDist < dist[edge.to])
91+
dist[edge.to] = newDist;
9492
}
9593
}
9694

9795
return dist;
9896
}
9997

100-
// Example usage of topological sort
10198
public static void main(String[] args) {
102-
103-
// Graph setup
10499
final int N = 7;
105100
Map<Integer, List<Edge>> graph = new HashMap<>();
106-
for (int i = 0; i < N; i++) graph.put(i, new ArrayList<>());
101+
for (int i = 0; i < N; i++)
102+
graph.put(i, new ArrayList<>());
103+
107104
graph.get(0).add(new Edge(0, 1, 3));
108105
graph.get(0).add(new Edge(0, 2, 2));
109106
graph.get(0).add(new Edge(0, 5, 3));
@@ -115,18 +112,10 @@ public static void main(String[] args) {
115112
graph.get(5).add(new Edge(5, 4, 7));
116113

117114
int[] ordering = topologicalSort(graph, N);
115+
System.out.println(Arrays.toString(ordering));
118116

119-
// // Prints: [6, 0, 5, 1, 2, 3, 4]
120-
System.out.println(java.util.Arrays.toString(ordering));
121-
122-
// Finds all the shortest paths starting at node 0
123117
Integer[] dists = dagShortestPath(graph, 0, N);
124-
125-
// Find the shortest path from 0 to 4 which is 8.0
126-
System.out.println(dists[4]);
127-
128-
// Find the shortest path from 0 to 6 which
129-
// is null since 6 is not reachable!
130-
System.out.println(dists[6]);
118+
System.out.println(dists[4]); // 8
119+
System.out.println(dists[6]); // null (unreachable)
131120
}
132121
}

0 commit comments

Comments
 (0)