diff --git a/README.md b/README.md index 106418ddf..d48667b3e 100644 --- a/README.md +++ b/README.md @@ -204,9 +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)** -- [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)** +- [: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))** @@ -224,12 +222,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 71bf96845..2351fe8a2 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", @@ -62,24 +55,10 @@ java_binary( runtime_deps = [":graphtheory"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchAdjacencyListIterative -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 +# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BreadthFirstSearchAdjacencyList java_binary( - name = "BreadthFirstSearchRecursive", - main_class = "com.williamfiset.algorithms.graphtheory.BreadthFirstSearchRecursive", + name = "BreadthFirstSearchAdjacencyList", + main_class = "com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyList", runtime_deps = [":graphtheory"], ) @@ -90,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", @@ -223,13 +195,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", @@ -258,13 +223,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/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/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/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/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]); - } -} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD index da8231b2c..6a610086b 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -46,24 +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"], - runtime_deps = JUNIT5_RUNTIME_DEPS, - 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"], + 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 62% rename from src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListTest.java index 917ef9b92..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; @@ -13,9 +13,11 @@ import java.util.List; import org.junit.jupiter.api.*; -public class BreadthFirstSearchAdjacencyListIterativeTest { +import com.williamfiset.algorithms.graphtheory.BellmanFordEdgeList; - BreadthFirstSearchAdjacencyListIterative solver; +public class BreadthFirstSearchAdjacencyListTest { + + BreadthFirstSearchAdjacencyList solver; @BeforeEach public void setup() { @@ -25,7 +27,7 @@ public void setup() { @Test public void testNullGraphInput() { assertThrows( - IllegalArgumentException.class, () -> new BreadthFirstSearchAdjacencyListIterative(null)); + IllegalArgumentException.class, () -> new BreadthFirstSearchAdjacencyList(null)); } @Test @@ -33,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); @@ -44,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); @@ -58,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); @@ -81,12 +83,24 @@ public void testShortestPathAgainstBellmanFord() { int s = (int) (random() * n); int e = (int) (random() * n); - solver = new BreadthFirstSearchAdjacencyListIterative(graph); - BellmanFordAdjacencyMatrix bfSolver = new BellmanFordAdjacencyMatrix(s, graph2); + solver = new BreadthFirstSearchAdjacencyList(graph); + + // 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); } } @@ -100,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; } 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; } 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."); } }