From 793b07a5c7e0ff566d5fe4c6327a63508f6950d2 Mon Sep 17 00:00:00 2001 From: William Fiset Date: Sun, 15 Mar 2026 21:57:01 -0700 Subject: [PATCH 1/4] Remove BellmanFordAdjacencyMatrix, migrate tests to BellmanFordEdgeList (#1294) The adjacency matrix variant is redundant with BellmanFordEdgeList and BellmanFordAdjacencyList. Tests that used it as an oracle now use BellmanFordEdgeList instead. Co-authored-by: Claude Opus 4.6 --- README.md | 1 - .../williamfiset/algorithms/graphtheory/BUILD | 7 - .../BellmanFordAdjacencyMatrix.java | 157 ------------------ ...FirstSearchAdjacencyListIterativeTest.java | 20 ++- .../graphtheory/FloydWarshallSolverTest.java | 30 +++- 5 files changed, 40 insertions(+), 175 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java diff --git a/README.md b/README.md index 106418ddf..8ac28587f 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,6 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Articulation points/cut vertices (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java) **- O(V+E)** - [Bellman-Ford (edge list, negative cycles, fast & optimized)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java) **- O(VE)** - [:movie_camera:](https://www.youtube.com/watch?v=lyw4FaxrwHg) [Bellman-Ford (adjacency list, negative cycles)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java) **- O(VE)** -- [Bellman-Ford (adjacency matrix, negative cycles)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java) **- O(V3)** - [:movie_camera:](https://www.youtube.com/watch?v=oDqjPvD54Ss) [Breadth first search (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java) **- O(V+E)** - [Breadth first search (adjacency list, fast queue)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java) **- O(V+E)** - [Bridges/cut edges (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java) **- O(V+E)** diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD index 71bf96845..e84345c4c 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -41,13 +41,6 @@ java_binary( runtime_deps = [":graphtheory"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BellmanFordAdjacencyMatrix -java_binary( - name = "BellmanFordAdjacencyMatrix", - main_class = "com.williamfiset.algorithms.graphtheory.BellmanFordAdjacencyMatrix", - runtime_deps = [":graphtheory"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BellmanFordEdgeList java_binary( name = "BellmanFordEdgeList", diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java deleted file mode 100644 index e14357a06..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java +++ /dev/null @@ -1,157 +0,0 @@ -/** - * An implementation of the Bellman-Ford algorithm. The algorithm finds the shortest path between a - * starting node and all other nodes in the graph. The algorithm also detects negative cycles. - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.graphtheory; - -import java.util.*; -import java.util.stream.Collectors; - -public class BellmanFordAdjacencyMatrix { - - private int n, start; - private boolean solved; - private double[] dist; - private Integer[] prev; - private double[][] matrix; - - /** - * An implementation of the Bellman-Ford algorithm. The algorithm finds the shortest path between - * a starting node and all other nodes in the graph. The algorithm also detects negative cycles. - * If a node is part of a negative cycle then the minimum cost for that node is set to - * Double.NEGATIVE_INFINITY. - * - * @param graph - An adjacency matrix containing directed edges forming the graph - * @param start - The id of the starting node - */ - public BellmanFordAdjacencyMatrix(int start, double[][] matrix) { - this.n = matrix.length; - this.start = start; - this.matrix = new double[n][n]; - - // Copy input adjacency matrix. - for (int i = 0; i < n; i++) this.matrix[i] = matrix[i].clone(); - } - - public double[] getShortestPaths() { - if (!solved) solve(); - return dist; - } - - public List reconstructShortestPath(int end) { - if (!solved) solve(); - LinkedList path = new LinkedList<>(); - if (dist[end] == Double.POSITIVE_INFINITY) return path; - for (int at = end; prev[at] != null; at = prev[at]) { - // Return null since there are an infinite number of shortest paths. - if (prev[at] == -1) return null; - path.addFirst(at); - } - path.addFirst(start); - return path; - } - - public void solve() { - if (solved) return; - - // Initialize the distance to all nodes to be infinity - // except for the start node which is zero. - dist = new double[n]; - java.util.Arrays.fill(dist, Double.POSITIVE_INFINITY); - dist[start] = 0; - - // Initialize prev array which will allows for shortest path - // reconstruction after the algorithm has terminated. - prev = new Integer[n]; - - // For each vertex, apply relaxation for all the edges - for (int k = 0; k < n - 1; k++) - for (int i = 0; i < n; i++) - for (int j = 0; j < n; j++) - if (dist[i] + matrix[i][j] < dist[j]) { - dist[j] = dist[i] + matrix[i][j]; - prev[j] = i; - } - - // Run algorithm a second time to detect which nodes are part - // of a negative cycle. A negative cycle has occurred if we - // can find a better path beyond the optimal solution. - for (int k = 0; k < n - 1; k++) - for (int i = 0; i < n; i++) - for (int j = 0; j < n; j++) - if (dist[i] + matrix[i][j] < dist[j]) { - dist[j] = Double.NEGATIVE_INFINITY; - prev[j] = -1; - } - - solved = true; - } - - public static void main(String[] args) { - - int n = 9; - double[][] graph = new double[n][n]; - - // Setup completely disconnected graph with the distance - // from a node to itself to be zero. - for (int i = 0; i < n; i++) { - java.util.Arrays.fill(graph[i], Double.POSITIVE_INFINITY); - graph[i][i] = 0; - } - - graph[0][1] = 1; - graph[1][2] = 1; - graph[2][4] = 1; - graph[4][3] = -3; - graph[3][2] = 1; - graph[1][5] = 4; - graph[1][6] = 4; - graph[5][6] = 5; - graph[6][7] = 4; - graph[5][7] = 3; - - int start = 0; - BellmanFordAdjacencyMatrix solver; - solver = new BellmanFordAdjacencyMatrix(start, graph); - double[] d = solver.getShortestPaths(); - - for (int i = 0; i < n; i++) - System.out.printf("The cost to get from node %d to %d is %.2f\n", start, i, d[i]); - - // Output: - // The cost to get from node 0 to 0 is 0.00 - // The cost to get from node 0 to 1 is 1.00 - // The cost to get from node 0 to 2 is -Infinity - // The cost to get from node 0 to 3 is -Infinity - // The cost to get from node 0 to 4 is -Infinity - // The cost to get from node 0 to 5 is 5.00 - // The cost to get from node 0 to 6 is 5.00 - // The cost to get from node 0 to 7 is 8.00 - // The cost to get from node 0 to 8 is Infinity - System.out.println(); - - for (int i = 0; i < n; i++) { - String strPath; - List path = solver.reconstructShortestPath(i); - if (path == null) { - strPath = "Infinite number of shortest paths."; - } else { - List nodes = path.stream().map(Object::toString).collect(Collectors.toList()); - strPath = String.join(" -> ", nodes); - } - System.out.printf("The shortest path from %d to %d is: [%s]\n", start, i, strPath); - } - // Outputs: - // The shortest path from 0 to 0 is: [0] - // The shortest path from 0 to 1 is: [0 -> 1] - // The shortest path from 0 to 2 is: [Infinite number of shortest paths.] - // The shortest path from 0 to 3 is: [Infinite number of shortest paths.] - // The shortest path from 0 to 4 is: [Infinite number of shortest paths.] - // The shortest path from 0 to 5 is: [0 -> 1 -> 5] - // The shortest path from 0 to 6 is: [0 -> 1 -> 6] - // The shortest path from 0 to 7 is: [0 -> 1 -> 5 -> 7] - // The shortest path from 0 to 8 is: [] - } -} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java index 917ef9b92..5161f0b54 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java @@ -13,6 +13,8 @@ import java.util.List; import org.junit.jupiter.api.*; +import com.williamfiset.algorithms.graphtheory.BellmanFordEdgeList; + public class BreadthFirstSearchAdjacencyListIterativeTest { BreadthFirstSearchAdjacencyListIterative solver; @@ -82,11 +84,23 @@ public void testShortestPathAgainstBellmanFord() { int s = (int) (random() * n); int e = (int) (random() * n); solver = new BreadthFirstSearchAdjacencyListIterative(graph); - BellmanFordAdjacencyMatrix bfSolver = new BellmanFordAdjacencyMatrix(s, graph2); + + // Convert adjacency matrix to edge list for BellmanFord + List edgeList = new ArrayList<>(); + for (int u = 0; u < n; u++) { + for (int v = 0; v < n; v++) { + if (u != v && graph2[u][v] != Double.POSITIVE_INFINITY) { + edgeList.add(new BellmanFordEdgeList.Edge(u, v, graph2[u][v])); + } + } + } + BellmanFordEdgeList.Edge[] edges = edgeList.toArray(new BellmanFordEdgeList.Edge[0]); + double[] dist = BellmanFordEdgeList.bellmanFord(edges, n, s); List p1 = solver.reconstructPath(s, e); - List p2 = bfSolver.reconstructShortestPath(e); - assertThat(p1.size()).isEqualTo(p2.size()); + // All edges have weight 1, so BF distance equals number of edges = path size - 1 + int expectedPathSize = (dist[e] == Double.POSITIVE_INFINITY) ? 0 : (int) dist[e] + 1; + assertThat(p1.size()).isEqualTo(expectedPathSize); } } diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java index fbb65afdf..543fd834d 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java @@ -2,6 +2,7 @@ import static com.google.common.truth.Truth.assertThat; +import com.williamfiset.algorithms.graphtheory.BellmanFordEdgeList.Edge; import java.util.*; import org.junit.jupiter.api.*; @@ -51,6 +52,20 @@ private static double[][] createMatrix(int n) { return m; } + /** Converts an adjacency matrix to an array of BellmanFordEdgeList edges. */ + private static Edge[] matrixToEdges(double[][] matrix) { + int n = matrix.length; + List edges = new ArrayList<>(); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && matrix[i][j] != Double.POSITIVE_INFINITY) { + edges.add(new Edge(i, j, matrix[i][j])); + } + } + } + return edges.toArray(new Edge[0]); + } + private static void addRandomEdges(double[][] matrix, int count, boolean allowNegativeEdges) { int n = matrix.length; while (count-- > 0) { @@ -125,7 +140,7 @@ public void testApspAgainstBellmanFord_nonNegativeEdgeWeights() { double[][] fw = new FloydWarshallSolver(m).getApspMatrix(); for (int s = 0; s < n; s++) { - double[] bf = new BellmanFordAdjacencyMatrix(s, m).getShortestPaths(); + double[] bf = BellmanFordEdgeList.bellmanFord(matrixToEdges(m), n, s); assertThat(bf).isEqualTo(fw[s]); } } @@ -144,7 +159,7 @@ public void testApspAgainstBellmanFord_withNegativeEdgeWeights() { double[][] fw = new FloydWarshallSolver(m).getApspMatrix(); for (int s = 0; s < n; s++) { - double[] bf = new BellmanFordAdjacencyMatrix(s, m).getShortestPaths(); + double[] bf = BellmanFordEdgeList.bellmanFord(matrixToEdges(m), n, s); assertThat(bf).isEqualTo(fw[s]); } } @@ -165,15 +180,16 @@ public void testPathReconstructionBellmanFord_nonNegativeEdgeWeights() { FloydWarshallSolver fwSolver = new FloydWarshallSolver(m); fwSolver.solve(); + Edge[] edges = matrixToEdges(m); for (int s = 0; s < n; s++) { - BellmanFordAdjacencyMatrix bfSolver = new BellmanFordAdjacencyMatrix(s, m); + double[] bf = BellmanFordEdgeList.bellmanFord(edges, n, s); for (int e = 0; e < n; e++) { - // Make sure that if 'fwp' returns null that 'bfp' also returns null or - // that if 'fwp' is not null that 'bfp' is also not null. + // FW returns null path when a negative cycle exists on the shortest path. + // BF marks such nodes with -Infinity. Verify they agree. List fwp = fwSolver.reconstructShortestPath(s, e); - List bfp = bfSolver.reconstructShortestPath(e); - if ((fwp == null) ^ (bfp == null)) { + boolean bfNegCycle = (bf[e] == Double.NEGATIVE_INFINITY); + if ((fwp == null) != bfNegCycle) { org.junit.Assert.fail("Mismatch."); } } From 5a8797a12d6f4f10c1fd8f829e7f3f8ed460404b Mon Sep 17 00:00:00 2001 From: William Fiset Date: Sun, 15 Mar 2026 22:02:21 -0700 Subject: [PATCH 2/4] Remove LazyPrimsAdjacencyMatrix and TopologicalSortAdjacencyMatrix (#1295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redundant adjacency matrix variants — the adjacency list versions (LazyPrimsAdjacencyList, EagerPrimsAdjacencyList, TopologicalSortAdjacencyList) are more efficient and already cover the same algorithms. Co-authored-by: Claude Opus 4.6 --- README.md | 2 - .../williamfiset/algorithms/graphtheory/BUILD | 14 --- .../graphtheory/LazyPrimsAdjacencyMatrix.java | 95 --------------- .../TopologicalSortAdjacencyMatrix.java | 108 ------------------ 4 files changed, 219 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java diff --git a/README.md b/README.md index 8ac28587f..a338ca19a 100644 --- a/README.md +++ b/README.md @@ -223,12 +223,10 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [:movie_camera:](https://www.youtube.com/watch?v=JZBQLXgSGfs) [Kruskal's min spanning tree algorithm (edge list, union find, lazy sorting)](src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java) **- O(Elog(E))** - [Kosaraju's strongly connected components algorithm (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/Kosaraju.java) **- O(V+E)** - [:movie_camera:](https://www.youtube.com/watch?v=jsmMtJpPnhU) [Prim's min spanning tree algorithm (lazy version, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java) **- O(Elog(E))** -- [Prim's min spanning tree algorithm (lazy version, adjacency matrix)](src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java) **- O(V2)** - [:movie_camera:](https://www.youtube.com/watch?v=xq3ABa-px_g) [Prim's min spanning tree algorithm (eager version, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java) **- O(Elog(V))** - [Steiner tree (minimum spanning tree generalization)](src/main/java/com/williamfiset/algorithms/graphtheory/SteinerTree.java) **- O(V3 + V2 _ 2T + V _ 3T)** - [:movie_camera:](https://www.youtube.com/watch?v=wUgWX0nc4NY) [Tarjan's strongly connected components algorithm (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java) **- O(V+E)** - [:movie_camera:](https://www.youtube.com/watch?v=eL-KzMXSXXI) [Topological sort (acyclic graph, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java) **- O(V+E)** -- [Topological sort (acyclic graph, adjacency matrix)](src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java) **- O(V2)** - [Traveling Salesman Problem (brute force)](src/main/java/com/williamfiset/algorithms/graphtheory/TspBruteForce.java) **- O(n!)** - [:movie_camera:](https://www.youtube.com/watch?v=cY4HiiFHO1o) [Traveling Salesman Problem (dynamic programming, iterative)](src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java) **- O(n22n)** - [Traveling Salesman Problem (dynamic programming, recursive)](src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java) **- O(n22n)** diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD index e84345c4c..44970cd21 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -216,13 +216,6 @@ java_binary( runtime_deps = [":graphtheory"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:LazyPrimsAdjacencyMatrix -java_binary( - name = "LazyPrimsAdjacencyMatrix", - main_class = "com.williamfiset.algorithms.graphtheory.LazyPrimsAdjacencyMatrix", - runtime_deps = [":graphtheory"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:SteinerTree java_binary( name = "SteinerTree", @@ -251,13 +244,6 @@ java_binary( runtime_deps = [":graphtheory"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:TopologicalSortAdjacencyMatrix -java_binary( - name = "TopologicalSortAdjacencyMatrix", - main_class = "com.williamfiset.algorithms.graphtheory.TopologicalSortAdjacencyMatrix", - runtime_deps = [":graphtheory"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:TspBruteForce java_binary( name = "TspBruteForce", diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java deleted file mode 100644 index 7f789c359..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * An implementation of the lazy Prim's algorithm with an adjacency matrix which upon visiting a new - * node adds all the edges to the min priority queue and also removes already seen edges when - * polling. - * - *

Time Complexity: O(V^2) - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.graphtheory; - -import java.util.*; - -public class LazyPrimsAdjacencyMatrix { - - static class Edge implements Comparable { - int to, cost; - - public Edge(int to, int cost) { - this.to = to; - this.cost = cost; - } - - @Override - public int compareTo(Edge other) { - return cost - other.cost; - } - } - - // Given an N*N undirected adjacency matrix, that is a - // graph with matrix[i][j] = matrix[j][i] for all i,j this method - // finds the minimum spanning tree cost using Prim's algorithm - public static Long prims(Integer[][] graph) { - - int n = graph.length; - long sum = 0, visitedNodes = 1; - PriorityQueue pq = new PriorityQueue<>(); - - boolean[] connected = new boolean[n]; - connected[0] = true; - - for (int i = 1; i < n; i++) if (graph[0][i] != null) pq.offer(new Edge(i, graph[0][i])); - - // Loop while the MST is not complete - while (visitedNodes != n && !pq.isEmpty()) { - - Edge edge = pq.poll(); - - if (!connected[edge.to]) { - - // Update minimum distances - for (int i = 0; i < n; i++) - if (!connected[i] && graph[edge.to][i] != null) pq.offer(new Edge(i, graph[edge.to][i])); - - connected[edge.to] = true; - sum += edge.cost; - visitedNodes++; - } - } - - // Make sure MST spans the whole graph - if (visitedNodes != n) return null; - return sum; - } - - // Example usage - public static void main(String[] args) { - - final int NUM_NODES = 10; - Integer[][] graph = new Integer[NUM_NODES][NUM_NODES]; - - // Make an undirected graph - graph[0][1] = graph[1][0] = 1; - graph[0][3] = graph[3][0] = 4; - graph[0][4] = graph[4][0] = 5; - graph[1][3] = graph[3][1] = 2; - graph[1][2] = graph[2][1] = 1; - graph[2][3] = graph[3][2] = 5; - graph[2][5] = graph[5][2] = 7; - graph[3][4] = graph[4][3] = 2; - graph[3][6] = graph[6][3] = 2; - graph[3][5] = graph[5][3] = 11; - graph[4][7] = graph[7][4] = 4; - graph[5][6] = graph[6][5] = 1; - graph[5][8] = graph[8][5] = 4; - graph[6][7] = graph[7][6] = 4; - graph[6][8] = graph[8][6] = 6; - graph[7][8] = graph[8][7] = 1; - graph[7][9] = graph[9][7] = 2; - graph[8][9] = graph[9][8] = 0; - - Long mstCost = prims(graph); - System.out.println("MST cost: " + mstCost); - } -} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java deleted file mode 100644 index d92c8a439..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * This Topological sort takes an adjacency matrix of an acyclic graph and returns an array with the - * indexes of the nodes in a (non unique) topological order which tells you how to process the nodes - * in the graph. More precisely from wiki: A topological ordering is a linear ordering of its - * vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the - * ordering. - * - *

Time Complexity: O(V^2) - * - * @author Micah Stairs - */ -package com.williamfiset.algorithms.graphtheory; - -public class TopologicalSortAdjacencyMatrix { - - // Performs the topological sort and returns the - // indexes of the nodes in topological order - public static int[] topologicalSort(Double[][] adj) { - - // Setup - int n = adj.length; - boolean[] visited = new boolean[n]; - int[] order = new int[n]; - int index = n - 1; - - // Visit each node - for (int u = 0; u < n; u++) if (!visited[u]) index = visit(adj, visited, order, index, u); - - // Return topological sort - return order; - } - - private static int visit(Double[][] adj, boolean[] visited, int[] order, int index, int u) { - - if (visited[u]) return index; - visited[u] = true; - - // Visit all neighbors - for (int v = 0; v < adj.length; v++) - if (adj[u][v] != null && !visited[v]) index = visit(adj, visited, order, index, v); - - // Place this node at the head of the list - order[index--] = u; - - return index; - } - - // A useful application of the topological sort is to find the shortest path - // between two nodes in a Directed Acyclic Graph (DAG). Given an adjacency matrix - // with double valued edge weights this method finds the shortest path from - // a start node to all other nodes in the graph. - public static double[] dagShortestPath(Double[][] adj, int start) { - - // Set up array used to maintain minimum distances from start - int n = adj.length; - double[] dist = new double[n]; - java.util.Arrays.fill(dist, Double.POSITIVE_INFINITY); - dist[start] = 0.0; - - // Process nodes in a topologically sorted order - for (int u : topologicalSort(adj)) - for (int v = 0; v < n; v++) { - if (adj[u][v] != null) { - double newDist = dist[u] + adj[u][v]; - if (newDist < dist[v]) dist[v] = newDist; - } - } - - // Return minimum distance - return dist; - } - - // Example usage of topological sort - public static void main(String[] args) { - - final int N = 7; - Double[][] adjMatrix = new Double[N][N]; - - adjMatrix[0][1] = 3.0; - adjMatrix[0][2] = 2.0; - adjMatrix[0][5] = 3.0; - - adjMatrix[1][3] = 1.0; - adjMatrix[1][2] = 6.0; - - adjMatrix[2][3] = 1.0; - adjMatrix[2][4] = 10.0; - - adjMatrix[3][4] = 5.0; - - adjMatrix[5][4] = 7.0; - - int[] ordering = topologicalSort(adjMatrix); - - // Prints: [6, 0, 5, 1, 2, 3, 4] - System.out.println(java.util.Arrays.toString(ordering)); - - // Find the shortest path from 0 to all other nodes - double[] dists = dagShortestPath(adjMatrix, 0); - - // Find the distance from 0 to 4 which is 8.0 - System.out.println(dists[4]); - - // Finds the shortest path from 0 to 6 - // prints Infinity (6 is not reachable!) - System.out.println(dists[6]); - } -} From 7985f2840e931a4ee7a1d3765daf48073c9e58fa Mon Sep 17 00:00:00 2001 From: William Fiset Date: Sun, 15 Mar 2026 22:45:04 -0700 Subject: [PATCH 3/4] Refactor BFS, remove FastQueue and Recursive duplicates (#1296) * Refactor BFS, remove FastQueue and Recursive duplicates Remove BreadthFirstSearchAdjacencyListIterativeFastQueue (custom IntQueue variant redundant with ArrayDeque) and BreadthFirstSearchRecursive (recursive BFS with queue-passing hack). Refactor the remaining BFS implementation with improved docs, final fields, and cleaner code style. Co-Authored-By: Claude Opus 4.6 * Rename BreadthFirstSearchAdjacencyListIterative to BreadthFirstSearchAdjacencyList Drop "Iterative" from the class and file name now that the recursive and fast-queue variants have been removed. Replace the 13-node example with a simpler 8-node graph including a visual diagram. Co-Authored-By: Claude Opus 4.6 * Remove cost from Edge class and simplify helpers for unweighted BFS BFS operates on unweighted graphs, so the cost field and weighted edge helpers are unnecessary. Rename addUnweightedUndirectedEdge to addUndirectedEdge. Co-Authored-By: Claude Opus 4.6 * Simplify reconstructPath: use LinkedList.addFirst to avoid reverse Co-Authored-By: Claude Opus 4.6 * Fix Integer reference comparison bug in reconstructPath Use intValue() instead of != to avoid reference comparison failure for node indices >= 128 (outside Integer cache range). Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- README.md | 3 +- .../williamfiset/algorithms/graphtheory/BUILD | 20 +-- .../BreadthFirstSearchAdjacencyList.java | 142 +++++++++++++++ ...adthFirstSearchAdjacencyListIterative.java | 144 --------------- ...SearchAdjacencyListIterativeFastQueue.java | 165 ------------------ .../BreadthFirstSearchRecursive.java | 91 ---------- .../williamfiset/algorithms/graphtheory/BUILD | 8 +- ... BreadthFirstSearchAdjacencyListTest.java} | 30 ++-- 8 files changed, 165 insertions(+), 438 deletions(-) create mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyList.java delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java rename src/test/java/com/williamfiset/algorithms/graphtheory/{BreadthFirstSearchAdjacencyListIterativeTest.java => BreadthFirstSearchAdjacencyListTest.java} (80%) diff --git a/README.md b/README.md index a338ca19a..d48667b3e 100644 --- a/README.md +++ b/README.md @@ -204,8 +204,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Articulation points/cut vertices (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java) **- O(V+E)** - [Bellman-Ford (edge list, negative cycles, fast & optimized)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java) **- O(VE)** - [:movie_camera:](https://www.youtube.com/watch?v=lyw4FaxrwHg) [Bellman-Ford (adjacency list, negative cycles)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java) **- O(VE)** -- [:movie_camera:](https://www.youtube.com/watch?v=oDqjPvD54Ss) [Breadth first search (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java) **- O(V+E)** -- [Breadth first search (adjacency list, fast queue)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=oDqjPvD54Ss) [Breadth first search (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyList.java) **- O(V+E)** - [Bridges/cut edges (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java) **- O(V+E)** - [Boruvkas (adjacency list, min spanning tree algorithm)](src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java) **- O(Elog(V))** - [Find connected components (adjacency list, union find)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java) **- O(Elog(E))** diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD index 44970cd21..5b76e8158 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -55,24 +55,10 @@ java_binary( runtime_deps = [":graphtheory"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchAdjacencyListIterative +# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchAdjacencyList java_binary( - name = "BreadthFirstSearchAdjacencyListIterative", - main_class = "com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative", - runtime_deps = [":graphtheory"], -) - -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchAdjacencyListIterativeFastQueue -java_binary( - name = "BreadthFirstSearchAdjacencyListIterativeFastQueue", - main_class = "com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterativeFastQueue", - runtime_deps = [":graphtheory"], -) - -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchRecursive -java_binary( - name = "BreadthFirstSearchRecursive", - main_class = "com.williamfiset.algorithms.graphtheory.BreadthFirstSearchRecursive", + name = "BreadthFirstSearchAdjacencyList", + main_class = "com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyList", runtime_deps = [":graphtheory"], ) diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyList.java new file mode 100644 index 000000000..ededd4c91 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyList.java @@ -0,0 +1,142 @@ +/** + * Breadth-First Search (BFS) — Adjacency List + * + *

Explores a graph level by level outward from a starting node using a + * FIFO queue. Because BFS visits nodes in order of increasing distance, + * it naturally finds the shortest path (fewest edges) between two nodes + * in an unweighted graph. + * + *

Algorithm: + *

    + *
  1. Enqueue the start node and mark it visited.
  2. + *
  3. Dequeue a node, then enqueue all its unvisited neighbours + * (marking each visited and recording the parent pointer).
  4. + *
  5. Repeat until the queue is empty.
  6. + *
  7. Reconstruct the shortest path by following parent pointers + * from the end node back to the start.
  8. + *
+ * + *

Time: O(V + E) + *

Space: O(V) + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.graphtheory; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +public class BreadthFirstSearchAdjacencyList { + + public static class Edge { + int from, to; + + public Edge(int from, int to) { + this.from = from; + this.to = to; + } + } + + private final int n; + private final List> graph; + private Integer[] prev; + + public BreadthFirstSearchAdjacencyList(List> graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph can not be null"); + } + this.n = graph.size(); + this.graph = graph; + } + + /** + * Returns the shortest path (fewest edges) from {@code start} to {@code end}. + * + * @return node indices along the path, or an empty list if unreachable. + */ + public List reconstructPath(int start, int end) { + bfs(start); + LinkedList path = new LinkedList<>(); + for (Integer at = end; at != null; at = prev[at]) { + path.addFirst(at); + } + if (path.isEmpty() || path.getFirst().intValue() != start) { + return List.of(); + } + return path; + } + + private void bfs(int start) { + prev = new Integer[n]; + boolean[] visited = new boolean[n]; + Deque queue = new ArrayDeque<>(n); + + queue.offer(start); + visited[start] = true; + + while (!queue.isEmpty()) { + int node = queue.poll(); + for (Edge edge : graph.get(node)) { + if (!visited[edge.to]) { + visited[edge.to] = true; + prev[edge.to] = node; + queue.offer(edge.to); + } + } + } + } + + /* Graph helpers */ + + public static List> createEmptyGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + graph.add(new ArrayList<>()); + } + return graph; + } + + public static void addDirectedEdge(List> graph, int u, int v) { + graph.get(u).add(new Edge(u, v)); + } + + public static void addUndirectedEdge(List> graph, int u, int v) { + addDirectedEdge(graph, u, v); + addDirectedEdge(graph, v, u); + } + + // ==================== Main ==================== + + // + // 0 --- 1 --- 2 + // | | | + // 3 4 5 + // \ / \ / + // 6 --- 7 + // + // Shortest path from 0 to 7: [0, 1, 4, 7] + // + public static void main(String[] args) { + int n = 8; + List> graph = createEmptyGraph(n); + + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + addUndirectedEdge(graph, 0, 3); + addUndirectedEdge(graph, 1, 4); + addUndirectedEdge(graph, 2, 5); + addUndirectedEdge(graph, 3, 6); + addUndirectedEdge(graph, 4, 6); + addUndirectedEdge(graph, 4, 7); + addUndirectedEdge(graph, 5, 7); + addUndirectedEdge(graph, 6, 7); + + BreadthFirstSearchAdjacencyList solver = new BreadthFirstSearchAdjacencyList(graph); + + System.out.println(solver.reconstructPath(0, 7)); // [0, 1, 4, 7] + System.out.println(solver.reconstructPath(3, 5)); // [3, 6, 7, 5] + } +} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java deleted file mode 100644 index 3ca3d1898..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java +++ /dev/null @@ -1,144 +0,0 @@ -/** - * An implementation of BFS with an adjacency list. - * - *

Time Complexity: O(V + E) - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.graphtheory; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.List; - -public class BreadthFirstSearchAdjacencyListIterative { - - public static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - - private int n; - private Integer[] prev; - private List> graph; - - public BreadthFirstSearchAdjacencyListIterative(List> graph) { - if (graph == null) throw new IllegalArgumentException("Graph can not be null"); - n = graph.size(); - this.graph = graph; - } - - /** - * Reconstructs the path (of nodes) from 'start' to 'end' inclusive. If the edges are unweighted - * then this method returns the shortest path from 'start' to 'end' - * - * @return An array of nodes indexes of the shortest path from 'start' to 'end'. If 'start' and - * 'end' are not connected then an empty array is returned. - */ - public List reconstructPath(int start, int end) { - bfs(start); - List path = new ArrayList<>(); - for (Integer at = end; at != null; at = prev[at]) path.add(at); - Collections.reverse(path); - if (path.get(0) == start) return path; - path.clear(); - return path; - } - - // Perform a breadth first search on a graph a starting node 'start'. - private void bfs(int start) { - prev = new Integer[n]; - boolean[] visited = new boolean[n]; - Deque queue = new ArrayDeque<>(n); - - // Start by visiting the 'start' node and add it to the queue. - queue.offer(start); - visited[start] = true; - - // Continue until the BFS is done. - while (!queue.isEmpty()) { - int node = queue.poll(); - List edges = graph.get(node); - - // Loop through all edges attached to this node. Mark nodes as visited once they're - // in the queue. This will prevent having duplicate nodes in the queue and speedup the BFS. - for (Edge edge : edges) { - if (!visited[edge.to]) { - visited[edge.to] = true; - prev[edge.to] = node; - queue.offer(edge.to); - } - } - } - } - - // Initialize an empty adjacency list that can hold up to n nodes. - public static List> createEmptyGraph(int n) { - List> graph = new ArrayList<>(n); - for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); - return graph; - } - - // Add a directed edge from node 'u' to node 'v' with cost 'cost'. - public static void addDirectedEdge(List> graph, int u, int v, int cost) { - graph.get(u).add(new Edge(u, v, cost)); - } - - // Add an undirected edge between nodes 'u' and 'v'. - public static void addUndirectedEdge(List> graph, int u, int v, int cost) { - addDirectedEdge(graph, u, v, cost); - addDirectedEdge(graph, v, u, cost); - } - - // Add an undirected unweighted edge between nodes 'u' and 'v'. The edge added - // will have a weight of 1 since its intended to be unweighted. - public static void addUnweightedUndirectedEdge(List> graph, int u, int v) { - addUndirectedEdge(graph, u, v, 1); - } - - /* BFS example. */ - - public static void main(String[] args) { - // BFS example #1 from slides. - final int n = 13; - List> graph = createEmptyGraph(n); - - addUnweightedUndirectedEdge(graph, 0, 7); - addUnweightedUndirectedEdge(graph, 0, 9); - addUnweightedUndirectedEdge(graph, 0, 11); - addUnweightedUndirectedEdge(graph, 7, 11); - addUnweightedUndirectedEdge(graph, 7, 6); - addUnweightedUndirectedEdge(graph, 7, 3); - addUnweightedUndirectedEdge(graph, 6, 5); - addUnweightedUndirectedEdge(graph, 3, 4); - addUnweightedUndirectedEdge(graph, 2, 3); - addUnweightedUndirectedEdge(graph, 2, 12); - addUnweightedUndirectedEdge(graph, 12, 8); - addUnweightedUndirectedEdge(graph, 8, 1); - addUnweightedUndirectedEdge(graph, 1, 10); - addUnweightedUndirectedEdge(graph, 10, 9); - addUnweightedUndirectedEdge(graph, 9, 8); - - BreadthFirstSearchAdjacencyListIterative solver; - solver = new BreadthFirstSearchAdjacencyListIterative(graph); - - int start = 10, end = 5; - List path = solver.reconstructPath(start, end); - System.out.printf("The shortest path from %d to %d is: [%s]\n", start, end, formatPath(path)); - // Prints: - // The shortest path from 10 to 5 is: [10 -> 9 -> 0 -> 7 -> 6 -> 5] - - } - - private static String formatPath(List path) { - return String.join( - " -> ", path.stream().map(Object::toString).collect(java.util.stream.Collectors.toList())); - } -} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java deleted file mode 100644 index a57870eb0..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java +++ /dev/null @@ -1,165 +0,0 @@ -/** - * An implementation of an iterative BFS with an adjacency list Time Complexity: O(V + E) - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.graphtheory; - -import java.util.*; - -// A custom implementation of a circular integer only queue which is -// extremely quick and lightweight. In terms of performance it can outperform -// java.util.ArrayDeque (Java's fastest queue implementation) by a factor of 40+! -// However, the downside is you need to know an upper bound on the number of elements -// that will be inside the queue at any given time for this queue to work. -class IntQueue { - - private int[] ar; - private int front, end, sz; - - // max_sz is the maximum number of items - // that can be in the queue at any given time - public IntQueue(int max_sz) { - front = end = 0; - this.sz = max_sz + 1; - ar = new int[sz]; - } - - public boolean isEmpty() { - return front == end; - } - - public int peek() { - return ar[front]; - } - - // Add an element to the queue - public void enqueue(int value) { - ar[end] = value; - if (++end == sz) end = 0; - if (end == front) throw new RuntimeException("Queue too small!"); - } - - // Make sure you check is the queue is not empty before calling dequeue! - public int dequeue() { - int ret_val = ar[front]; - if (++front == sz) front = 0; - return ret_val; - } -} - -public class BreadthFirstSearchAdjacencyListIterativeFastQueue { - - static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - - // Perform a breadth first search on a graph with n nodes - // from a starting point to count the number of nodes - // in a given component. - static int bfs(Map> graph, int start, int n) { - - int count = 0; - boolean[] visited = new boolean[n]; - IntQueue queue = new IntQueue(n + 1); - - // For each breadth first search layer gets separated by a DEPTH_TOKEN - // to easily augment this method for additional functionality - int DEPTH_TOKEN = -1; - - // Start by visiting the starting node - queue.enqueue(start); - queue.enqueue(DEPTH_TOKEN); - visited[start] = true; - - // Continue until the BFS is done - while (true) { - - Integer node = queue.dequeue(); - - // If we encounter a depth token this means that we - // have finished the current frontier and are about - // to start the new layer (some of which may already - // be in the queue) or have reached the end. - if (node == DEPTH_TOKEN) { - - // No more nodes to process - if (queue.isEmpty()) break; - - // Add another DEPTH_TOKEN - queue.enqueue(DEPTH_TOKEN); - - } else { - - count++; - - List edges = graph.get(node); - if (edges != null) { - - // Loop through all edges attached to this node. Mark nodes as - // visited once they're in the queue. This will prevent having - // duplicate nodes in the queue and speedup the BFS. - for (Edge edge : edges) { - if (!visited[edge.to]) { - visited[edge.to] = true; - queue.enqueue(edge.to); - } - } - } - } - } - - return count; - } - - // Example usage of DFS - public static void main(String[] args) { - - // Create a fully connected graph - int numNodes = 8; - Map> graph = new HashMap<>(); - addDirectedEdge(graph, 1, 2, 1); - addDirectedEdge(graph, 1, 2, 1); // Double edge - addDirectedEdge(graph, 1, 3, 1); - addDirectedEdge(graph, 2, 4, 1); - addDirectedEdge(graph, 2, 5, 1); - addDirectedEdge(graph, 3, 6, 1); - addDirectedEdge(graph, 3, 7, 1); - addDirectedEdge(graph, 2, 2, 1); // Self loop - addDirectedEdge(graph, 2, 3, 1); - addDirectedEdge(graph, 6, 2, 1); - addDirectedEdge(graph, 1, 6, 1); - - long nodeCount = bfs(graph, 0, numNodes); - System.out.println("DFS node count starting at node 0: " + nodeCount); - - nodeCount = bfs(graph, 2, numNodes); - System.out.println("DFS node count starting at node 4: " + nodeCount); - - // Complete graph with self loops - graph.clear(); - numNodes = 100; - for (int i = 0; i < numNodes; i++) - for (int j = 0; j < numNodes; j++) addDirectedEdge(graph, i, j, 1); - - nodeCount = bfs(graph, 6, numNodes); - System.out.println("BFS node count starting at node 6: " + nodeCount); - if (nodeCount != 100) System.err.println("Error with BFS"); - } - - // Helper method to setup graph - private static void addDirectedEdge(Map> graph, int from, int to, int cost) { - List list = graph.get(from); - if (list == null) { - list = new ArrayList(); - graph.put(from, list); - } - list.add(new Edge(from, to, cost)); - } -} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java deleted file mode 100644 index 40ccdf654..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * This is an implementation of doing a breadth first search recursively with a slight cheat of - * passing in a queue as an argument to the function. A breadth first search - * - *

Time Complexity: O(V + E) - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.graphtheory; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -public class BreadthFirstSearchRecursive { - - // Each breadth first search layer gets separated by a DEPTH_TOKEN. - // DEPTH_TOKENs help count the distance from one node to another because - // we can increment the depth counter each time a DEPTH_TOKEN is encountered - public static int DEPTH_TOKEN = -1; - - // Computes the eccentricity (distance to furthest node) from the starting node. - public static int bfs(List> graph, int start, int n) { - boolean[] visited = new boolean[n]; - Queue queue = new LinkedList<>(); - queue.offer(start); - queue.offer(DEPTH_TOKEN); - return bfs(visited, queue, graph); - } - - private static int bfs(boolean[] visited, Queue queue, List> graph) { - - int at = queue.poll(); - - if (at == DEPTH_TOKEN) { - queue.offer(DEPTH_TOKEN); - return 1; - } - - // This node is already visited. - if (visited[at]) return 0; - - // Visit this node. - visited[at] = true; - - // Add all neighbors to queue. - List neighbors = graph.get(at); - if (neighbors != null) for (int next : neighbors) if (!visited[next]) queue.add(next); - - int depth = 0; - - while (true) { - // Stop when the queue is empty (i.e there's only one depth token remaining) - if (queue.size() == 1 && queue.peek() == DEPTH_TOKEN) break; - - // The depth is the sum of all DEPTH_TOKENS encountered. - depth += bfs(visited, queue, graph); - } - - return depth; - } - - public static void main(String[] args) { - int n = 14; - List> graph = new ArrayList<>(); - for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); - - addUndirectedEdge(graph, 0, 1); - addUndirectedEdge(graph, 0, 2); - addUndirectedEdge(graph, 0, 3); - addUndirectedEdge(graph, 2, 9); - addUndirectedEdge(graph, 8, 2); - addUndirectedEdge(graph, 3, 4); - addUndirectedEdge(graph, 10, 11); - addUndirectedEdge(graph, 12, 13); - addUndirectedEdge(graph, 3, 5); - addUndirectedEdge(graph, 5, 7); - addUndirectedEdge(graph, 5, 6); - addUndirectedEdge(graph, 0, 10); - addUndirectedEdge(graph, 11, 12); - - System.out.printf("BFS depth: %d\n", bfs(graph, 12, n)); - } - - // Helper method to setup graph - private static void addUndirectedEdge(List> graph, int from, int to) { - graph.get(from).add(to); - graph.get(to).add(from); - } -} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD index da8231b2c..60c23f1bd 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -46,13 +46,13 @@ java_test( deps = TEST_DEPS, ) -# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchAdjacencyListIterativeTest +# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchAdjacencyListTest java_test( - name = "BreadthFirstSearchAdjacencyListIterativeTest", - srcs = ["BreadthFirstSearchAdjacencyListIterativeTest.java"], + name = "BreadthFirstSearchAdjacencyListTest", + srcs = ["BreadthFirstSearchAdjacencyListTest.java"], main_class = "org.junit.platform.console.ConsoleLauncher", use_testrunner = False, - args = ["--select-class=com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterativeTest"], + args = ["--select-class=com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListTest"], runtime_deps = JUNIT5_RUNTIME_DEPS, deps = TEST_DEPS, ) diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListTest.java similarity index 80% rename from src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListTest.java index 5161f0b54..c3fa96b45 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListTest.java @@ -1,9 +1,9 @@ package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; -import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.Edge; -import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.addUnweightedUndirectedEdge; -import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.createEmptyGraph; +import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyList.Edge; +import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyList.addUndirectedEdge; +import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyList.createEmptyGraph; import static java.lang.Math.max; import static java.lang.Math.random; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -15,9 +15,9 @@ import com.williamfiset.algorithms.graphtheory.BellmanFordEdgeList; -public class BreadthFirstSearchAdjacencyListIterativeTest { +public class BreadthFirstSearchAdjacencyListTest { - BreadthFirstSearchAdjacencyListIterative solver; + BreadthFirstSearchAdjacencyList solver; @BeforeEach public void setup() { @@ -27,7 +27,7 @@ public void setup() { @Test public void testNullGraphInput() { assertThrows( - IllegalArgumentException.class, () -> new BreadthFirstSearchAdjacencyListIterative(null)); + IllegalArgumentException.class, () -> new BreadthFirstSearchAdjacencyList(null)); } @Test @@ -35,7 +35,7 @@ public void testSingletonGraph() { int n = 1; List> graph = createEmptyGraph(n); - solver = new BreadthFirstSearchAdjacencyListIterative(graph); + solver = new BreadthFirstSearchAdjacencyList(graph); List path = solver.reconstructPath(0, 0); List expected = new ArrayList<>(); expected.add(0); @@ -46,8 +46,8 @@ public void testSingletonGraph() { public void testTwoNodeGraph() { int n = 2; List> graph = createEmptyGraph(n); - addUnweightedUndirectedEdge(graph, 0, 1); - solver = new BreadthFirstSearchAdjacencyListIterative(graph); + addUndirectedEdge(graph, 0, 1); + solver = new BreadthFirstSearchAdjacencyList(graph); List expected = new ArrayList<>(); expected.add(0); @@ -60,10 +60,10 @@ public void testTwoNodeGraph() { @Test public void testThreeNodeGraph() { int n = 3; - List> graph = BreadthFirstSearchAdjacencyListIterative.createEmptyGraph(n); - addUnweightedUndirectedEdge(graph, 0, 1); - addUnweightedUndirectedEdge(graph, 2, 1); - solver = new BreadthFirstSearchAdjacencyListIterative(graph); + List> graph = BreadthFirstSearchAdjacencyList.createEmptyGraph(n); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 2, 1); + solver = new BreadthFirstSearchAdjacencyList(graph); List expected = new ArrayList<>(); expected.add(0); @@ -83,7 +83,7 @@ public void testShortestPathAgainstBellmanFord() { int s = (int) (random() * n); int e = (int) (random() * n); - solver = new BreadthFirstSearchAdjacencyListIterative(graph); + solver = new BreadthFirstSearchAdjacencyList(graph); // Convert adjacency matrix to edge list for BellmanFord List edgeList = new ArrayList<>(); @@ -114,7 +114,7 @@ public static double[][] generateRandomGraph(List> graph1, int n) { int u = (int) (random() * n); int v = (int) (random() * n); if (!edgeMatrix[u][v]) { - addUnweightedUndirectedEdge(graph1, u, v); + addUndirectedEdge(graph1, u, v); graph2[u][v] = graph2[v][u] = 1; edgeMatrix[u][v] = edgeMatrix[v][u] = true; } From b6462bd9c71549baebf75de111a15014fe1a182c Mon Sep 17 00:00:00 2001 From: William Fiset Date: Sun, 15 Mar 2026 23:07:44 -0700 Subject: [PATCH 4/4] Refactor BridgesAdjacencyList: add docs, clean up code (#1297) * Refactor BridgesAdjacencyList: add docs, clean up code Co-Authored-By: Claude Opus 4.6 * Change findBridges return type from flat List to List Each bridge is now returned as an int[2] pair instead of two consecutive elements in a flat list. Updated both test files accordingly. Co-Authored-By: Claude Opus 4.6 * Clarify findBridges Javadoc with concrete example Co-Authored-By: Claude Opus 4.6 * Document parallel-edge limitation in BridgesAdjacencyList Co-Authored-By: Claude Opus 4.6 * Remove BridgesAdjacencyListIterative and its test Redundant with the recursive BridgesAdjacencyList implementation. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../williamfiset/algorithms/graphtheory/BUILD | 7 - .../graphtheory/BridgesAdjacencyList.java | 140 +++++++++------ .../BridgesAdjacencyListIterative.java | 136 --------------- .../williamfiset/algorithms/graphtheory/BUILD | 11 -- .../BridgesAdjacencyListIterativeTest.java | 162 ------------------ .../graphtheory/BridgesAdjacencyListTest.java | 14 +- 6 files changed, 95 insertions(+), 375 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java delete mode 100644 src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD index 5b76e8158..2351fe8a2 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -69,13 +69,6 @@ java_binary( runtime_deps = [":graphtheory"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BridgesAdjacencyListIterative -java_binary( - name = "BridgesAdjacencyListIterative", - main_class = "com.williamfiset.algorithms.graphtheory.BridgesAdjacencyListIterative", - runtime_deps = [":graphtheory"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ChinesePostmanProblem java_binary( name = "ChinesePostmanProblem", diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java index 49151169e..dba299230 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java @@ -1,9 +1,33 @@ /** - * Finds all the bridges on an undirected graph. + * Bridge Edges (Cut Edges) — Adjacency List * - *

Test against HackerEarth online judge at: + *

A bridge is an edge whose removal disconnects the graph (or increases + * the number of connected components). This implementation uses Tarjan's + * DFS-based algorithm with low-link values. + * + *

An edge (u, v) is a bridge if no vertex in the subtree rooted at v + * (in the DFS tree) has a back edge to u or any ancestor of u: + * + *

   ids[u] < low[v]
+ * + *

Limitation: This implementation assumes a simple graph (no + * parallel/multi-edges between the same pair of nodes). If parallel edges exist, + * the algorithm may incorrectly report an edge as a bridge even though a second + * edge still connects the two nodes. This is because the parent-skip logic + * ({@code to == parent}) skips all occurrences of the parent in the + * adjacency list, rather than only the single tree edge used to arrive at the + * current node. + * + *

Works on disconnected graphs by running DFS from every unvisited node. + * + *

See also: {@link ArticulationPointsAdjacencyList} for finding cut vertices. + * + *

Tested against HackerEarth online judge at: * https://www.hackerearth.com/practice/algorithms/graphs/articulation-points-and-bridges/tutorial * + *

Time: O(V + E) + *

Space: O(V) + * * @author William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.graphtheory; @@ -15,64 +39,99 @@ public class BridgesAdjacencyList { - private int n, id; - private int[] low, ids; + private final int n; + private final List> graph; private boolean solved; + private int id; + private int[] low, ids; private boolean[] visited; - private List> graph; - private List bridges; + private List bridges; public BridgesAdjacencyList(List> graph, int n) { - if (graph == null || n <= 0 || graph.size() != n) throw new IllegalArgumentException(); + if (graph == null || n <= 0 || graph.size() != n) { + throw new IllegalArgumentException(); + } this.graph = graph; this.n = n; } - // Returns a list of pairs of nodes indicating which nodes form bridges. - // The returned list is always of even length and indexes (2*i, 2*i+1) form a - // pair. For example, nodes at indexes (0, 1) are a pair, (2, 3) are another - // pair, etc... - public List findBridges() { - if (solved) return bridges; + /** + * Returns a list of bridge edges. Each element is an {@code int[]} of length 2 + * where {@code [0]} and {@code [1]} are the node indices on either side of the bridge. + * For example, if node 2 and node 5 are connected by a bridge, the entry is {@code {2, 5}}. + */ + public List findBridges() { + if (solved) { + return bridges; + } id = 0; - low = new int[n]; // Low link values - ids = new int[n]; // Nodes ids + low = new int[n]; + ids = new int[n]; visited = new boolean[n]; - bridges = new ArrayList<>(); - // Finds all bridges in the graph across various connected components. - for (int i = 0; i < n; i++) if (!visited[i]) dfs(i, -1, bridges); + // Run DFS from each unvisited node to handle disconnected components. + for (int i = 0; i < n; i++) { + if (!visited[i]) { + dfs(i, -1); + } + } solved = true; return bridges; } - private void dfs(int at, int parent, List bridges) { - + private void dfs(int at, int parent) { visited[at] = true; low[at] = ids[at] = ++id; - for (Integer to : graph.get(at)) { - if (to == parent) continue; + for (int to : graph.get(at)) { + if (to == parent) { + continue; + } if (!visited[to]) { - dfs(to, at, bridges); + dfs(to, at); low[at] = min(low[at], low[to]); + // If no vertex in the subtree rooted at 'to' can reach 'at' or above, + // then removing edge (at, to) would disconnect the graph. if (ids[at] < low[to]) { - bridges.add(at); - bridges.add(to); + bridges.add(new int[] {at, to}); } } else { + // Back edge: update low-link to the earliest reachable ancestor. low[at] = min(low[at], ids[to]); } } } - /* Example usage: */ + /* Graph helpers */ - public static void main(String[] args) { + public static List> createGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + graph.add(new ArrayList<>()); + } + return graph; + } + public static void addEdge(List> graph, int from, int to) { + graph.get(from).add(to); + graph.get(to).add(from); + } + + // ==================== Main ==================== + + // + // 0 --- 1 + // | / + // 2 -------- 5 --- 6 + // | | | + // 3 --- 4 8 --- 7 + // + // Bridges: (2,3), (3,4), (2,5) + // + public static void main(String[] args) { int n = 9; List> graph = createGraph(n); @@ -88,29 +147,10 @@ public static void main(String[] args) { addEdge(graph, 8, 5); BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List bridges = solver.findBridges(); - - // Prints: - // Bridge between nodes: 3 and 4 - // Bridge between nodes: 2 and 3 - // Bridge between nodes: 2 and 5 - for (int i = 0; i < bridges.size() / 2; i++) { - int node1 = bridges.get(2 * i); - int node2 = bridges.get(2 * i + 1); - System.out.printf("Bridge between nodes: %d and %d\n", node1, node2); - } - } - - // Initialize graph with 'n' nodes. - public static List> createGraph(int n) { - List> graph = new ArrayList<>(); - for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); - return graph; - } + List bridges = solver.findBridges(); - // Add undirected edge to graph. - public static void addEdge(List> graph, int from, int to) { - graph.get(from).add(to); - graph.get(to).add(from); + for (int[] bridge : bridges) { + System.out.printf("Bridge between nodes: %d and %d\n", bridge[0], bridge[1]); + } } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java deleted file mode 100644 index 0fb3d5b7c..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Finds all the bridges on an undirected graph. - * - *

Test against HackerEarth online judge at: - * https://www.hackerearth.com/practice/algorithms/graphs/articulation-points-and-bridges/tutorial - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.graphtheory; - -import static java.lang.Math.min; - -import java.util.*; - -public class BridgesAdjacencyListIterative { - - private int n, id; - private int[] low, ids; - private boolean solved; - private boolean[] visited; - private List> graph; - private List bridges; - - private static int CALLBACK_TOKEN = -2; - - public BridgesAdjacencyListIterative(List> graph, int n) { - if (graph == null || n <= 0 || graph.size() != n) throw new IllegalArgumentException(); - this.graph = graph; - this.n = n; - } - - // Returns a list of pairs of nodes indicating which nodes form bridges. - // The returned list is always of even length and indexes (2*i, 2*i+1) form a - // pair. For example, nodes are indexes (0, 1) are a pair, (2, 3) are another - // pair, etc... - public List findBridges() { - if (solved) return bridges; - - id = 0; - low = new int[n]; // Low link values - ids = new int[n]; // Nodes ids - visited = new boolean[n]; - - bridges = new ArrayList<>(); - - // Finds all bridges even if the graph is not one single connected component. - for (int i = 0; i < n; i++) { - if (visited[i]) continue; - - Deque stack = new ArrayDeque<>(); - Deque parentStack = new ArrayDeque<>(); - stack.push(i); - parentStack.push(-1); - - while (!stack.isEmpty()) { - int at = stack.pop(); - - if (at == CALLBACK_TOKEN) { - at = stack.pop(); - int to = stack.pop(); - low[at] = min(low[at], low[to]); - if (ids[at] < low[to]) { - bridges.add(at); - bridges.add(to); - } - continue; - } - - int parent = parentStack.pop(); - if (!visited[at]) { - low[at] = ids[at] = ++id; - visited[at] = true; - - List edges = graph.get(at); - for (Integer to : edges) { - if (to == parent) continue; - if (!visited[to]) { - stack.push(to); - stack.push(at); - stack.push(CALLBACK_TOKEN); - stack.push(to); - parentStack.push(at); - } else { - low[at] = min(low[at], ids[to]); - } - } - } - } - } - - solved = true; - return bridges; - } - - /* Example usage: */ - - public static void main(String[] args) { - - int n = 10; - List> graph = createGraph(n); - - addEdge(graph, 0, 1); - addEdge(graph, 0, 2); - addEdge(graph, 1, 2); - addEdge(graph, 1, 3); - addEdge(graph, 2, 3); - addEdge(graph, 1, 4); - addEdge(graph, 2, 7); - addEdge(graph, 4, 6); - addEdge(graph, 4, 5); - addEdge(graph, 5, 6); - addEdge(graph, 7, 8); - addEdge(graph, 7, 9); - - BridgesAdjacencyListIterative solver = new BridgesAdjacencyListIterative(graph, n); - List bridges = solver.findBridges(); - for (int i = 0; i < bridges.size() / 2; i++) { - int node1 = bridges.get(2 * i); - int node2 = bridges.get(2 * i + 1); - System.out.printf("BRIDGE between nodes: %d and %d\n", node1, node2); - } - } - - // Initialize graph with 'n' nodes. - public static List> createGraph(int n) { - List> graph = new ArrayList<>(); - for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); - return graph; - } - - // Add undirected edge to graph. - public static void addEdge(List> graph, int from, int to) { - graph.get(from).add(to); - graph.get(to).add(from); - } -} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD index 60c23f1bd..6a610086b 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -57,17 +57,6 @@ java_test( deps = TEST_DEPS, ) -# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BridgesAdjacencyListIterativeTest -java_test( - name = "BridgesAdjacencyListIterativeTest", - srcs = ["BridgesAdjacencyListIterativeTest.java"], - main_class = "org.junit.platform.console.ConsoleLauncher", - use_testrunner = False, - args = ["--select-class=com.williamfiset.algorithms.graphtheory.BridgesAdjacencyListIterativeTest"], - runtime_deps = JUNIT5_RUNTIME_DEPS, - deps = TEST_DEPS, -) - # bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BridgesAdjacencyListTest java_test( name = "BridgesAdjacencyListTest", diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java deleted file mode 100644 index 8e9de87c0..000000000 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.williamfiset.algorithms.graphtheory; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.collect.ImmutableList; -import java.util.*; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.*; - -public class BridgesAdjacencyListIterativeTest { - - // Initialize graph with 'n' nodes. - public static List> createGraph(int n) { - List> graph = new ArrayList<>(); - for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); - return graph; - } - - // Add undirected edge to graph. - public static void addEdge(List> graph, int from, int to) { - graph.get(from).add(to); - graph.get(to).add(from); - } - - // Every edge should be a bridge if the input a tree - @Test - public void testTreeCase() { - - int n = 12; - List> graph = createGraph(n); - addEdge(graph, 1, 0); - addEdge(graph, 0, 2); - addEdge(graph, 2, 5); - addEdge(graph, 5, 6); - addEdge(graph, 5, 11); - addEdge(graph, 5, 4); - addEdge(graph, 4, 10); - addEdge(graph, 4, 3); - addEdge(graph, 3, 7); - addEdge(graph, 7, 8); - addEdge(graph, 7, 9); - - BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List> sortedBridges = getSortedBridges(solver.findBridges()); - - List> expected = - ImmutableList.of( - Pair.of(0, 1), - Pair.of(0, 2), - Pair.of(2, 5), - Pair.of(5, 6), - Pair.of(5, 11), - Pair.of(4, 5), - Pair.of(4, 10), - Pair.of(3, 4), - Pair.of(3, 7), - Pair.of(7, 8), - Pair.of(7, 9)); - - assertThat(sortedBridges).containsExactlyElementsIn(expected); - } - - // Every edge should be a bridge if the input a tree - @Test - public void graphWithCyclesTest() { - - int n = 12; - List> graph = createGraph(n); - addEdge(graph, 1, 0); - addEdge(graph, 0, 2); - addEdge(graph, 3, 1); - addEdge(graph, 2, 5); - addEdge(graph, 5, 6); - addEdge(graph, 5, 11); - addEdge(graph, 5, 4); - addEdge(graph, 4, 10); - addEdge(graph, 4, 3); - addEdge(graph, 3, 7); - addEdge(graph, 7, 8); - addEdge(graph, 7, 9); - addEdge(graph, 11, 6); - - BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List> sortedBridges = getSortedBridges(solver.findBridges()); - - List> expected = - ImmutableList.of(Pair.of(3, 7), Pair.of(7, 8), Pair.of(7, 9), Pair.of(4, 10)); - - assertThat(sortedBridges).containsExactlyElementsIn(expected); - } - - @Test - public void testGraphInSlides() { - int n = 9; - List> graph = createGraph(n); - addEdge(graph, 0, 1); - addEdge(graph, 1, 2); - addEdge(graph, 2, 3); - addEdge(graph, 2, 5); - addEdge(graph, 2, 0); - addEdge(graph, 3, 4); - addEdge(graph, 5, 6); - addEdge(graph, 6, 7); - addEdge(graph, 7, 8); - addEdge(graph, 8, 5); - - BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List> sortedBridges = getSortedBridges(solver.findBridges()); - - List> expected = - ImmutableList.of(Pair.of(2, 3), Pair.of(3, 4), Pair.of(2, 5)); - - assertThat(sortedBridges).containsExactlyElementsIn(expected); - } - - @Test - public void testDisconnectedGraph() { - int n = 11; - List> graph = createGraph(n); - addEdge(graph, 0, 1); - addEdge(graph, 2, 1); - - addEdge(graph, 3, 4); - - addEdge(graph, 5, 7); - addEdge(graph, 5, 6); - addEdge(graph, 6, 7); - addEdge(graph, 8, 7); - addEdge(graph, 8, 9); - addEdge(graph, 8, 10); - - BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List> sortedBridges = getSortedBridges(solver.findBridges()); - - List> expected = - ImmutableList.of( - Pair.of(0, 1), - Pair.of(1, 2), - Pair.of(3, 4), - Pair.of(7, 8), - Pair.of(8, 9), - Pair.of(8, 10)); - - assertThat(sortedBridges).containsExactlyElementsIn(expected); - } - - private static List> getSortedBridges(List bridgeNodes) { - List> bridges = new ArrayList<>(); - for (int i = 0; i < bridgeNodes.size(); i += 2) { - int node1 = bridgeNodes.get(i); - int node2 = bridgeNodes.get(i + 1); - Pair pair; - if (node1 < node2) { - pair = Pair.of(node1, node2); - } else { - pair = Pair.of(node2, node1); - } - bridges.add(pair); - } - return bridges; - } -} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java index 595b024ff..a5c50b15d 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java @@ -144,18 +144,14 @@ public void testDisconnectedGraph() { assertThat(sortedBridges).containsExactlyElementsIn(expected); } - private static List> getSortedBridges(List bridgeNodes) { + private static List> getSortedBridges(List bridgeEdges) { List> bridges = new ArrayList<>(); - for (int i = 0; i < bridgeNodes.size(); i += 2) { - int node1 = bridgeNodes.get(i); - int node2 = bridgeNodes.get(i + 1); - Pair pair; - if (node1 < node2) { - pair = Pair.of(node1, node2); + for (int[] edge : bridgeEdges) { + if (edge[0] < edge[1]) { + bridges.add(Pair.of(edge[0], edge[1])); } else { - pair = Pair.of(node2, node1); + bridges.add(Pair.of(edge[1], edge[0])); } - bridges.add(pair); } return bridges; }