From 6ada4592cdc3b0f89d9ee47b43d2214a595e1f67 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 19:35:16 +0530 Subject: [PATCH 1/8] =?UTF-8?q?Add=20Yen=E2=80=99s=20K-shortest=20loopless?= =?UTF-8?q?=20paths=20with=20tests=20and=20index=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DIRECTORY.md | 1 + .../graph/YensKShortestPaths.java | 244 ++++++++++++++++++ .../graph/YensKShortestPathsTest.java | 76 ++++++ 3 files changed, 321 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/YensKShortestPaths.java create mode 100644 src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 8fbcbaaec96e..3bfedac64b89 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -354,6 +354,7 @@ - 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java) - 📄 [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java) - 📄 [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java) + - 📄 [YensKShortestPaths](src/main/java/com/thealgorithms/graph/YensKShortestPaths.java) - 📁 **greedyalgorithms** - 📄 [ActivitySelection](src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java) - 📄 [BandwidthAllocation](src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java) diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java new file mode 100644 index 000000000000..a25370958b3b --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -0,0 +1,244 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; + +/** + * Yen's algorithm for finding K loopless shortest paths in a directed graph with non-negative edge weights. + * + *

Input is an adjacency matrix of edge weights. A value of -1 indicates no edge. + * All existing edge weights must be non-negative. Zero-weight edges are allowed.

+ * + *

References: + * - Wikipedia: Yen's algorithm (https://en.wikipedia.org/wiki/Yen%27s_algorithm) + * - Dijkstra's algorithm for the base shortest path computation.

+ */ +public final class YensKShortestPaths { + + private YensKShortestPaths() { + } + + /** + * Compute up to k loopless shortest paths from src to dst using Yen's algorithm. + * + * @param weights adjacency matrix; weights[u][v] = -1 means no edge; otherwise non-negative edge weight + * @param src source vertex index + * @param dst destination vertex index + * @param k maximum number of paths to return (k >= 1) + * @return list of paths, each path is a list of vertex indices in order from src to dst + * @throws IllegalArgumentException on invalid inputs (null, non-square, negatives on existing edges, bad indices, k < 1) + */ + public static List> kShortestPaths(int[][] weights, int src, int dst, int k) { + validate(weights, src, dst, k); + final int n = weights.length; + // Make a defensive copy to avoid mutating caller's matrix + int[][] w = new int[n][n]; + for (int i = 0; i < n; i++) { + w[i] = Arrays.copyOf(weights[i], n); + } + + List A = new ArrayList<>(); + PriorityQueue B = new PriorityQueue<>(); // min-heap by cost then lexicographic nodes + Set seen = new HashSet<>(); // deduplicate candidate paths by node sequence key + + Path first = dijkstra(w, src, dst, new boolean[n]); + if (first == null) { + return List.of(); + } + A.add(first); + + for (int kIdx = 1; kIdx < k; kIdx++) { + Path lastPath = A.get(kIdx - 1); + List lastNodes = lastPath.nodes; + for (int i = 0; i < lastNodes.size() - 1; i++) { + int spurNode = lastNodes.get(i); + List rootPath = lastNodes.subList(0, i + 1); + + // Build modified graph: remove edges that would recreate same root + next edge as any A path + int[][] wMod = cloneMatrix(w); + + for (Path p : A) { + if (startsWith(p.nodes, rootPath) && p.nodes.size() > i + 1) { + int u = p.nodes.get(i); + int v = p.nodes.get(i + 1); + wMod[u][v] = -1; // remove edge + } + } + // Prevent revisiting nodes in rootPath (loopless constraint), except spurNode itself + boolean[] blocked = new boolean[n]; + for (int j = 0; j < rootPath.size() - 1; j++) { + blocked[rootPath.get(j)] = true; + } + + Path spurPath = dijkstra(wMod, spurNode, dst, blocked); + if (spurPath != null) { + // concatenate rootPath (excluding spurNode at end) + spurPath + List totalNodes = new ArrayList<>(rootPath); + // spurPath.nodes starts with spurNode; avoid duplication + for (int idx = 1; idx < spurPath.nodes.size(); idx++) { + totalNodes.add(spurPath.nodes.get(idx)); + } + long rootCost = pathCost(w, rootPath); + long totalCost = rootCost + spurPath.cost; // spurPath.cost covers from spurNode to dst + Path candidate = new Path(totalNodes, totalCost); + String key = candidate.key(); + if (!seen.contains(key)) { + B.add(candidate); + seen.add(key); + } + } + } + if (B.isEmpty()) { + break; + } + A.add(B.poll()); + } + + // Map to list of node indices for output + List> result = new ArrayList<>(A.size()); + for (Path p : A) { + result.add(new ArrayList<>(p.nodes)); + } + return result; + } + + private static void validate(int[][] weights, int src, int dst, int k) { + if (weights == null || weights.length == 0) { + throw new IllegalArgumentException("Weights matrix must not be null or empty"); + } + int n = weights.length; + for (int i = 0; i < n; i++) { + if (weights[i] == null || weights[i].length != n) { + throw new IllegalArgumentException("Weights matrix must be square"); + } + for (int j = 0; j < n; j++) { + int val = weights[i][j]; + if (val < -1) { + throw new IllegalArgumentException("Weights must be -1 (no edge) or >= 0"); + } + } + } + if (src < 0 || dst < 0 || src >= n || dst >= n) { + throw new IllegalArgumentException("Invalid src/dst indices"); + } + if (k < 1) { + throw new IllegalArgumentException("k must be >= 1"); + } + if (src == dst) { + // allowed: path is [src] with cost 0 (handled by dijkstra) + } + } + + private static boolean startsWith(List list, List prefix) { + if (prefix.size() > list.size()) return false; + for (int i = 0; i < prefix.size(); i++) { + if (!Objects.equals(list.get(i), prefix.get(i))) return false; + } + return true; + } + + private static int[][] cloneMatrix(int[][] a) { + int n = a.length; + int[][] b = new int[n][n]; + for (int i = 0; i < n; i++) b[i] = Arrays.copyOf(a[i], n); + return b; + } + + private static long pathCost(int[][] w, List nodes) { + long cost = 0; + for (int i = 0; i + 1 < nodes.size(); i++) { + int u = nodes.get(i), v = nodes.get(i + 1); + int c = w[u][v]; + if (c < 0) return Long.MAX_VALUE / 4; // invalid + cost += c; + } + return cost; + } + + private static Path dijkstra(int[][] w, int src, int dst, boolean[] blocked) { + int n = w.length; + final long INF = Long.MAX_VALUE / 4; + long[] dist = new long[n]; + int[] parent = new int[n]; + Arrays.fill(dist, INF); + Arrays.fill(parent, -1); + PriorityQueue pq = new PriorityQueue<>(); + if (blocked[src]) return null; + dist[src] = 0; + pq.add(new Node(src, 0)); + while (!pq.isEmpty()) { + Node cur = pq.poll(); + if (cur.dist != dist[cur.u]) continue; + if (cur.u == dst) break; + for (int v = 0; v < n; v++) { + int wuv = w[cur.u][v]; + if (wuv >= 0 && !blocked[v]) { + long nd = cur.dist + wuv; + if (nd < dist[v]) { + dist[v] = nd; + parent[v] = cur.u; + pq.add(new Node(v, nd)); + } + } + } + } + if (dist[dst] >= INF) { + // If src==dst and not blocked, the path is trivial with cost 0 + if (src == dst) { + List nodes = new ArrayList<>(); + nodes.add(src); + return new Path(nodes, 0); + } + return null; + } + // Reconstruct path + List nodes = new ArrayList<>(); + int cur = dst; + while (cur != -1) { + nodes.add(0, cur); + cur = parent[cur]; + } + return new Path(nodes, dist[dst]); + } + + private static final class Node implements Comparable { + final int u; + final long dist; + Node(int u, long dist) { + this.u = u; + this.dist = dist; + } + public int compareTo(Node o) { + return Long.compare(this.dist, o.dist); + } + } + + private static final class Path implements Comparable { + final List nodes; + final long cost; + Path(List nodes, long cost) { + this.nodes = nodes; + this.cost = cost; + } + String key() { + return nodes.toString(); + } + @Override + public int compareTo(Path o) { + int c = Long.compare(this.cost, o.cost); + if (c != 0) return c; + // tie-break lexicographically on nodes + int m = Math.min(this.nodes.size(), o.nodes.size()); + for (int i = 0; i < m; i++) { + int a = this.nodes.get(i), b = o.nodes.get(i); + if (a != b) return Integer.compare(a, b); + } + return Integer.compare(this.nodes.size(), o.nodes.size()); + } + } +} diff --git a/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java b/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java new file mode 100644 index 000000000000..0225265bf9ec --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java @@ -0,0 +1,76 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class YensKShortestPathsTest { + + @Test + @DisplayName("Basic K-shortest paths on small directed graph") + void basicKPaths() { + // Graph (directed) with non-negative weights, -1 = no edge + // 0 -> 1 (1), 0 -> 2 (2), 1 -> 3 (1), 2 -> 3 (1), 0 -> 3 (5), 1 -> 2 (1) + int N = 4; + int[][] w = new int[N][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + w[i][j] = -1; + } + } + w[0][1] = 1; + w[0][2] = 2; + w[1][3] = 1; + w[2][3] = 1; + w[0][3] = 5; + w[1][2] = 1; + + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 3, 3); + // Expected K=3 loopless shortest paths from 0 to 3, ordered by total cost: + // 1) 0-1-3 (cost 2) + // 2) 0-2-3 (cost 3) + // 3) 0-1-2-3 (cost 3) -> tie with 0-2-3; tie-broken lexicographically by nodes + assertEquals(3, paths.size()); + assertEquals(List.of(0, 1, 3), paths.get(0)); + assertEquals(List.of(0, 1, 2, 3), paths.get(1)); // lexicographically before [0,2,3] + assertEquals(List.of(0, 2, 3), paths.get(2)); + } + + @Test + @DisplayName("K larger than available paths returns only existing ones") + void kLargerThanAvailable() { + int[][] w = {{-1, 1, -1}, {-1, -1, 1}, {-1, -1, -1}}; + // Only one simple path 0->1->2 + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 2, 5); + assertEquals(1, paths.size()); + assertEquals(List.of(0, 1, 2), paths.get(0)); + } + + @Test + @DisplayName("No path returns empty list") + void noPath() { + int[][] w = {{-1, -1}, {-1, -1}}; + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 1, 3); + assertEquals(0, paths.size()); + } + + @Test + @DisplayName("Source equals destination returns trivial path") + void sourceEqualsDestination() { + int[][] w = {{-1, 1}, {-1, -1}}; + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 0, 2); + // First path is [0] + assertEquals(1, paths.size()); + assertEquals(List.of(0), paths.get(0)); + } + + @Test + @DisplayName("Negative weight entries (other than -1) are rejected") + void negativeWeightsRejected() { + int[][] w = {{-1, -2}, {-1, -1}}; + assertThrows(IllegalArgumentException.class, () -> YensKShortestPaths.kShortestPaths(w, 0, 1, 2)); + } +} From 434848f4ce9db92b62d92c88fd74da7238680ba6 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 20:19:50 +0530 Subject: [PATCH 2/8] style: fix Checkstyle in Yens algorithm and tests --- .../graph/YensKShortestPaths.java | 68 ++++++++++++------- .../graph/YensKShortestPathsTest.java | 8 +-- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java index a25370958b3b..57eaafb84cc4 100644 --- a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -42,18 +42,18 @@ public static List> kShortestPaths(int[][] weights, int src, int d w[i] = Arrays.copyOf(weights[i], n); } - List A = new ArrayList<>(); - PriorityQueue B = new PriorityQueue<>(); // min-heap by cost then lexicographic nodes + List a = new ArrayList<>(); + PriorityQueue b = new PriorityQueue<>(); // min-heap by cost then lexicographic nodes Set seen = new HashSet<>(); // deduplicate candidate paths by node sequence key Path first = dijkstra(w, src, dst, new boolean[n]); if (first == null) { return List.of(); } - A.add(first); + a.add(first); for (int kIdx = 1; kIdx < k; kIdx++) { - Path lastPath = A.get(kIdx - 1); + Path lastPath = a.get(kIdx - 1); List lastNodes = lastPath.nodes; for (int i = 0; i < lastNodes.size() - 1; i++) { int spurNode = lastNodes.get(i); @@ -62,7 +62,7 @@ public static List> kShortestPaths(int[][] weights, int src, int d // Build modified graph: remove edges that would recreate same root + next edge as any A path int[][] wMod = cloneMatrix(w); - for (Path p : A) { + for (Path p : a) { if (startsWith(p.nodes, rootPath) && p.nodes.size() > i + 1) { int u = p.nodes.get(i); int v = p.nodes.get(i + 1); @@ -88,20 +88,20 @@ public static List> kShortestPaths(int[][] weights, int src, int d Path candidate = new Path(totalNodes, totalCost); String key = candidate.key(); if (!seen.contains(key)) { - B.add(candidate); + b.add(candidate); seen.add(key); } } } - if (B.isEmpty()) { + if (b.isEmpty()) { break; } - A.add(B.poll()); + a.add(b.poll()); } // Map to list of node indices for output - List> result = new ArrayList<>(A.size()); - for (Path p : A) { + List> result = new ArrayList<>(a.size()); + for (Path p : a) { result.add(new ArrayList<>(p.nodes)); } return result; @@ -135,9 +135,13 @@ private static void validate(int[][] weights, int src, int dst, int k) { } private static boolean startsWith(List list, List prefix) { - if (prefix.size() > list.size()) return false; + if (prefix.size() > list.size()) { + return false; + } for (int i = 0; i < prefix.size(); i++) { - if (!Objects.equals(list.get(i), prefix.get(i))) return false; + if (!Objects.equals(list.get(i), prefix.get(i))) { + return false; + } } return true; } @@ -145,16 +149,21 @@ private static boolean startsWith(List list, List prefix) { private static int[][] cloneMatrix(int[][] a) { int n = a.length; int[][] b = new int[n][n]; - for (int i = 0; i < n; i++) b[i] = Arrays.copyOf(a[i], n); + for (int i = 0; i < n; i++) { + b[i] = Arrays.copyOf(a[i], n); + } return b; } private static long pathCost(int[][] w, List nodes) { long cost = 0; for (int i = 0; i + 1 < nodes.size(); i++) { - int u = nodes.get(i), v = nodes.get(i + 1); + int u = nodes.get(i); + int v = nodes.get(i + 1); int c = w[u][v]; - if (c < 0) return Long.MAX_VALUE / 4; // invalid + if (c < 0) { + return Long.MAX_VALUE / 4; // invalid + } cost += c; } return cost; @@ -162,19 +171,25 @@ private static long pathCost(int[][] w, List nodes) { private static Path dijkstra(int[][] w, int src, int dst, boolean[] blocked) { int n = w.length; - final long INF = Long.MAX_VALUE / 4; + final long inf = Long.MAX_VALUE / 4; long[] dist = new long[n]; int[] parent = new int[n]; - Arrays.fill(dist, INF); + Arrays.fill(dist, inf); Arrays.fill(parent, -1); PriorityQueue pq = new PriorityQueue<>(); - if (blocked[src]) return null; + if (blocked[src]) { + return null; + } dist[src] = 0; pq.add(new Node(src, 0)); while (!pq.isEmpty()) { Node cur = pq.poll(); - if (cur.dist != dist[cur.u]) continue; - if (cur.u == dst) break; + if (cur.dist != dist[cur.u]) { + continue; + } + if (cur.u == dst) { + break; + } for (int v = 0; v < n; v++) { int wuv = w[cur.u][v]; if (wuv >= 0 && !blocked[v]) { @@ -187,7 +202,7 @@ private static Path dijkstra(int[][] w, int src, int dst, boolean[] blocked) { } } } - if (dist[dst] >= INF) { + if (dist[dst] >= inf) { // If src==dst and not blocked, the path is trivial with cost 0 if (src == dst) { List nodes = new ArrayList<>(); @@ -231,12 +246,17 @@ String key() { @Override public int compareTo(Path o) { int c = Long.compare(this.cost, o.cost); - if (c != 0) return c; + if (c != 0) { + return c; + } // tie-break lexicographically on nodes int m = Math.min(this.nodes.size(), o.nodes.size()); for (int i = 0; i < m; i++) { - int a = this.nodes.get(i), b = o.nodes.get(i); - if (a != b) return Integer.compare(a, b); + int aNode = this.nodes.get(i); + int bNode = o.nodes.get(i); + if (aNode != bNode) { + return Integer.compare(aNode, bNode); + } } return Integer.compare(this.nodes.size(), o.nodes.size()); } diff --git a/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java b/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java index 0225265bf9ec..1de37a310939 100644 --- a/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java +++ b/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java @@ -14,10 +14,10 @@ class YensKShortestPathsTest { void basicKPaths() { // Graph (directed) with non-negative weights, -1 = no edge // 0 -> 1 (1), 0 -> 2 (2), 1 -> 3 (1), 2 -> 3 (1), 0 -> 3 (5), 1 -> 2 (1) - int N = 4; - int[][] w = new int[N][N]; - for (int i = 0; i < N; i++) { - for (int j = 0; j < N; j++) { + int n = 4; + int[][] w = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { w[i][j] = -1; } } From 6298350b77075cc24b5ac60120e5f54a637f6849 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 20:30:43 +0530 Subject: [PATCH 3/8] fix: resolve SpotBugs in Yens algorithm --- src/main/java/com/thealgorithms/graph/YensKShortestPaths.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java index 57eaafb84cc4..b615b2966cbc 100644 --- a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -87,9 +87,8 @@ public static List> kShortestPaths(int[][] weights, int src, int d long totalCost = rootCost + spurPath.cost; // spurPath.cost covers from spurNode to dst Path candidate = new Path(totalNodes, totalCost); String key = candidate.key(); - if (!seen.contains(key)) { + if (seen.add(key)) { b.add(candidate); - seen.add(key); } } } From 4822dd93f474dcab30aac01612e9a8ef5e8e37d1 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 20:43:33 +0530 Subject: [PATCH 4/8] fix (PMD): rename short variables in the code --- .../graph/YensKShortestPaths.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java index b615b2966cbc..a90240baeb26 100644 --- a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -37,36 +37,36 @@ public static List> kShortestPaths(int[][] weights, int src, int d validate(weights, src, dst, k); final int n = weights.length; // Make a defensive copy to avoid mutating caller's matrix - int[][] w = new int[n][n]; + int[][] weightsCopy = new int[n][n]; for (int i = 0; i < n; i++) { - w[i] = Arrays.copyOf(weights[i], n); + weightsCopy[i] = Arrays.copyOf(weights[i], n); } - List a = new ArrayList<>(); - PriorityQueue b = new PriorityQueue<>(); // min-heap by cost then lexicographic nodes + List shortestPaths = new ArrayList<>(); + PriorityQueue candidates = new PriorityQueue<>(); // min-heap by cost then lexicographic nodes Set seen = new HashSet<>(); // deduplicate candidate paths by node sequence key - Path first = dijkstra(w, src, dst, new boolean[n]); + Path first = dijkstra(weightsCopy, src, dst, new boolean[n]); if (first == null) { return List.of(); } - a.add(first); + shortestPaths.add(first); for (int kIdx = 1; kIdx < k; kIdx++) { - Path lastPath = a.get(kIdx - 1); + Path lastPath = shortestPaths.get(kIdx - 1); List lastNodes = lastPath.nodes; for (int i = 0; i < lastNodes.size() - 1; i++) { int spurNode = lastNodes.get(i); List rootPath = lastNodes.subList(0, i + 1); // Build modified graph: remove edges that would recreate same root + next edge as any A path - int[][] wMod = cloneMatrix(w); + int[][] modifiedWeights = cloneMatrix(weightsCopy); - for (Path p : a) { + for (Path p : shortestPaths) { if (startsWith(p.nodes, rootPath) && p.nodes.size() > i + 1) { int u = p.nodes.get(i); int v = p.nodes.get(i + 1); - wMod[u][v] = -1; // remove edge + modifiedWeights[u][v] = -1; // remove edge } } // Prevent revisiting nodes in rootPath (loopless constraint), except spurNode itself @@ -75,7 +75,7 @@ public static List> kShortestPaths(int[][] weights, int src, int d blocked[rootPath.get(j)] = true; } - Path spurPath = dijkstra(wMod, spurNode, dst, blocked); + Path spurPath = dijkstra(modifiedWeights, spurNode, dst, blocked); if (spurPath != null) { // concatenate rootPath (excluding spurNode at end) + spurPath List totalNodes = new ArrayList<>(rootPath); @@ -83,24 +83,24 @@ public static List> kShortestPaths(int[][] weights, int src, int d for (int idx = 1; idx < spurPath.nodes.size(); idx++) { totalNodes.add(spurPath.nodes.get(idx)); } - long rootCost = pathCost(w, rootPath); + long rootCost = pathCost(weightsCopy, rootPath); long totalCost = rootCost + spurPath.cost; // spurPath.cost covers from spurNode to dst Path candidate = new Path(totalNodes, totalCost); String key = candidate.key(); if (seen.add(key)) { - b.add(candidate); + candidates.add(candidate); } } } - if (b.isEmpty()) { + if (candidates.isEmpty()) { break; } - a.add(b.poll()); + shortestPaths.add(candidates.poll()); } // Map to list of node indices for output - List> result = new ArrayList<>(a.size()); - for (Path p : a) { + List> result = new ArrayList<>(shortestPaths.size()); + for (Path p : shortestPaths) { result.add(new ArrayList<>(p.nodes)); } return result; @@ -154,12 +154,12 @@ private static int[][] cloneMatrix(int[][] a) { return b; } - private static long pathCost(int[][] w, List nodes) { + private static long pathCost(int[][] weights, List nodes) { long cost = 0; for (int i = 0; i + 1 < nodes.size(); i++) { int u = nodes.get(i); int v = nodes.get(i + 1); - int c = w[u][v]; + int c = weights[u][v]; if (c < 0) { return Long.MAX_VALUE / 4; // invalid } @@ -168,35 +168,35 @@ private static long pathCost(int[][] w, List nodes) { return cost; } - private static Path dijkstra(int[][] w, int src, int dst, boolean[] blocked) { - int n = w.length; + private static Path dijkstra(int[][] weights, int src, int dst, boolean[] blocked) { + int n = weights.length; final long inf = Long.MAX_VALUE / 4; long[] dist = new long[n]; int[] parent = new int[n]; Arrays.fill(dist, inf); Arrays.fill(parent, -1); - PriorityQueue pq = new PriorityQueue<>(); + PriorityQueue queue = new PriorityQueue<>(); if (blocked[src]) { return null; } dist[src] = 0; - pq.add(new Node(src, 0)); - while (!pq.isEmpty()) { - Node cur = pq.poll(); - if (cur.dist != dist[cur.u]) { + queue.add(new Node(src, 0)); + while (!queue.isEmpty()) { + Node current = queue.poll(); + if (current.dist != dist[current.u]) { continue; } - if (cur.u == dst) { + if (current.u == dst) { break; } for (int v = 0; v < n; v++) { - int wuv = w[cur.u][v]; - if (wuv >= 0 && !blocked[v]) { - long nd = cur.dist + wuv; - if (nd < dist[v]) { - dist[v] = nd; - parent[v] = cur.u; - pq.add(new Node(v, nd)); + int edgeWeight = weights[current.u][v]; + if (edgeWeight >= 0 && !blocked[v]) { + long newDist = current.dist + edgeWeight; + if (newDist < dist[v]) { + dist[v] = newDist; + parent[v] = current.u; + queue.add(new Node(v, newDist)); } } } From 8e5e1118d0df05658c12a9e756e6c949ec569eb6 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 20:54:50 +0530 Subject: [PATCH 5/8] (pmd): code fixes --- .../graph/YensKShortestPaths.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java index a90240baeb26..f330629c5740 100644 --- a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -23,6 +23,9 @@ public final class YensKShortestPaths { private YensKShortestPaths() { } + private static final int NO_EDGE = -1; + private static final long INF_COST = Long.MAX_VALUE / 4; + /** * Compute up to k loopless shortest paths from src to dst using Yen's algorithm. * @@ -66,7 +69,7 @@ public static List> kShortestPaths(int[][] weights, int src, int d if (startsWith(p.nodes, rootPath) && p.nodes.size() > i + 1) { int u = p.nodes.get(i); int v = p.nodes.get(i + 1); - modifiedWeights[u][v] = -1; // remove edge + modifiedWeights[u][v] = NO_EDGE; // remove edge } } // Prevent revisiting nodes in rootPath (loopless constraint), except spurNode itself @@ -117,7 +120,7 @@ private static void validate(int[][] weights, int src, int dst, int k) { } for (int j = 0; j < n; j++) { int val = weights[i][j]; - if (val < -1) { + if (val < NO_EDGE) { throw new IllegalArgumentException("Weights must be -1 (no edge) or >= 0"); } } @@ -159,18 +162,18 @@ private static long pathCost(int[][] weights, List nodes) { for (int i = 0; i + 1 < nodes.size(); i++) { int u = nodes.get(i); int v = nodes.get(i + 1); - int c = weights[u][v]; - if (c < 0) { - return Long.MAX_VALUE / 4; // invalid + int edgeCost = weights[u][v]; + if (edgeCost < 0) { + return INF_COST; // invalid } - cost += c; + cost += edgeCost; } return cost; } private static Path dijkstra(int[][] weights, int src, int dst, boolean[] blocked) { int n = weights.length; - final long inf = Long.MAX_VALUE / 4; + final long inf = INF_COST; long[] dist = new long[n]; int[] parent = new int[n]; Arrays.fill(dist, inf); @@ -244,13 +247,13 @@ String key() { } @Override public int compareTo(Path o) { - int c = Long.compare(this.cost, o.cost); - if (c != 0) { - return c; + int costCmp = Long.compare(this.cost, o.cost); + if (costCmp != 0) { + return costCmp; } // tie-break lexicographically on nodes - int m = Math.min(this.nodes.size(), o.nodes.size()); - for (int i = 0; i < m; i++) { + int minLength = Math.min(this.nodes.size(), o.nodes.size()); + for (int i = 0; i < minLength; i++) { int aNode = this.nodes.get(i); int bNode = o.nodes.get(i); if (aNode != bNode) { From 65dd4d9f17214b9362b1445298474f2e8919bae3 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 22:26:35 +0530 Subject: [PATCH 6/8] fix(bloomfilter): hash arrays by content to satisfy array membership tests --- .../bloomfilter/BloomFilter.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java index d60b95110fc2..c84f25bd78e6 100644 --- a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java +++ b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java @@ -1,5 +1,6 @@ package com.thealgorithms.datastructures.bloomfilter; +import java.util.Arrays; import java.util.BitSet; /** @@ -115,7 +116,7 @@ private static class Hash { * @return the computed hash value */ public int compute(T key) { - return index * asciiString(String.valueOf(key)); + return index * contentHash(key); } /** @@ -135,5 +136,31 @@ private int asciiString(String word) { } return sum; } + + /** + * Computes a content-based hash for arrays; falls back to ASCII-sum of String value otherwise. + */ + private int contentHash(Object key) { + if (key instanceof int[]) { + return Arrays.hashCode((int[]) key); + } else if (key instanceof long[]) { + return Arrays.hashCode((long[]) key); + } else if (key instanceof byte[]) { + return Arrays.hashCode((byte[]) key); + } else if (key instanceof short[]) { + return Arrays.hashCode((short[]) key); + } else if (key instanceof char[]) { + return Arrays.hashCode((char[]) key); + } else if (key instanceof boolean[]) { + return Arrays.hashCode((boolean[]) key); + } else if (key instanceof float[]) { + return Arrays.hashCode((float[]) key); + } else if (key instanceof double[]) { + return Arrays.hashCode((double[]) key); + } else if (key instanceof Object[]) { + return Arrays.deepHashCode((Object[]) key); + } + return asciiString(String.valueOf(key)); + } } } From 81055dcc368af8da3735dcaa002f1eff8034d909 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 22:40:39 +0530 Subject: [PATCH 7/8] style(pmd): fix EmptyControlStatement in validate() by returning early when src==dst --- src/main/java/com/thealgorithms/graph/YensKShortestPaths.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java index f330629c5740..41254c9f0349 100644 --- a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -133,6 +133,7 @@ private static void validate(int[][] weights, int src, int dst, int k) { } if (src == dst) { // allowed: path is [src] with cost 0 (handled by dijkstra) + return; } } From 31e8229304a8f7a66a634c9b97f1efa36016c217 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 22:50:24 +0530 Subject: [PATCH 8/8] style(pmd): remove unnecessary return in validate() --- src/main/java/com/thealgorithms/graph/YensKShortestPaths.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java index 41254c9f0349..dfc8386de6ce 100644 --- a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -131,10 +131,6 @@ private static void validate(int[][] weights, int src, int dst, int k) { if (k < 1) { throw new IllegalArgumentException("k must be >= 1"); } - if (src == dst) { - // allowed: path is [src] with cost 0 (handled by dijkstra) - return; - } } private static boolean startsWith(List list, List prefix) {