diff --git a/src/main/java/com/thealgorithms/graph/ZeroOneBfs.java b/src/main/java/com/thealgorithms/graph/ZeroOneBfs.java new file mode 100644 index 000000000000..53181a654215 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/ZeroOneBfs.java @@ -0,0 +1,77 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; + +/** + * 0-1 BFS for shortest paths on graphs with edges weighted 0 or 1. + * + *

Time Complexity: O(V + E). Space Complexity: O(V). + * + *

References: + *

+ */ +public final class ZeroOneBfs { + + private ZeroOneBfs() { + // Utility class; do not instantiate. + } + + /** + * Computes shortest distances from {@code src} in a graph whose edges have weight 0 or 1. + * + * @param n the number of vertices, labeled {@code 0..n-1} + * @param adj adjacency list; for each vertex u, {@code adj.get(u)} is a list of pairs + * {@code (v, w)} where {@code v} is a neighbor and {@code w} is 0 or 1 + * @param src the source vertex + * @return an array of distances; {@code Integer.MAX_VALUE} denotes unreachable + * @throws IllegalArgumentException if {@code n < 0}, {@code src} is out of range, + * or any edge has weight other than 0 or 1 + */ + public static int[] shortestPaths(int n, List> adj, int src) { + if (n < 0 || src < 0 || src >= n) { + throw new IllegalArgumentException("Invalid n or src"); + } + int[] dist = new int[n]; + Arrays.fill(dist, Integer.MAX_VALUE); + Deque dq = new ArrayDeque<>(); + + dist[src] = 0; + dq.addFirst(src); + + while (!dq.isEmpty()) { + int u = dq.pollFirst(); + List edges = adj.get(u); + if (edges == null) { + continue; + } + for (int[] e : edges) { + if (e == null || e.length < 2) { + continue; + } + int v = e[0]; + int w = e[1]; + if (v < 0 || v >= n) { + continue; // ignore bad edges + } + if (w != 0 && w != 1) { + throw new IllegalArgumentException("Edge weight must be 0 or 1"); + } + int nd = dist[u] + w; + if (nd < dist[v]) { + dist[v] = nd; + if (w == 0) { + dq.addFirst(v); + } else { + dq.addLast(v); + } + } + } + } + return dist; + } +} diff --git a/src/test/java/com/thealgorithms/graph/ZeroOneBfsTest.java b/src/test/java/com/thealgorithms/graph/ZeroOneBfsTest.java new file mode 100644 index 000000000000..5de9eadf0930 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/ZeroOneBfsTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ZeroOneBfsTest { + + // Helper to build adjacency list with capacity n + private static List> makeAdj(int n) { + List> adj = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + adj.add(new ArrayList<>()); + } + return adj; + } + + @Test + void simpleLineGraph() { + int n = 4; + List> adj = makeAdj(n); + // 0 --0--> 1 --1--> 2 --0--> 3 + adj.get(0).add(new int[] {1, 0}); + adj.get(1).add(new int[] {2, 1}); + adj.get(2).add(new int[] {3, 0}); + + int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0); + assertArrayEquals(new int[] {0, 0, 1, 1}, dist); + } + + @Test + void parallelEdgesPreferZero() { + int n = 3; + List> adj = makeAdj(n); + // Two edges 0->1: weight 1 and weight 0. Algorithm should choose 0. + adj.get(0).add(new int[] {1, 1}); + adj.get(0).add(new int[] {1, 0}); + adj.get(1).add(new int[] {2, 1}); + + int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0); + assertArrayEquals(new int[] {0, 0, 1}, dist); + } + + @Test + void unreachableNodes() { + int n = 3; + List> adj = makeAdj(n); + adj.get(0).add(new int[] {1, 0}); + int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0); + // node 2 unreachable -> Integer.MAX_VALUE + assertArrayEquals(new int[] {0, 0, Integer.MAX_VALUE}, dist); + } + + @Test + void invalidArgs() { + int n = 2; + List> adj = makeAdj(n); + // invalid weight + adj.get(0).add(new int[] {1, 2}); + assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, 0)); + // invalid src + assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, -1)); + assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, 2)); + } +}