Skip to content

Commit 7ccc9b8

Browse files
Merge branch 'master' into feat/FlattenMultilevelLinkedList
2 parents 5eac1b7 + 87ad52c commit 7ccc9b8

File tree

5 files changed

+370
-3
lines changed

5 files changed

+370
-3
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@
354354
- 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
355355
- 📄 [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java)
356356
- 📄 [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java)
357+
- 📄 [YensKShortestPaths](src/main/java/com/thealgorithms/graph/YensKShortestPaths.java)
357358
- 📁 **greedyalgorithms**
358359
- 📄 [ActivitySelection](src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java)
359360
- 📄 [BandwidthAllocation](src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java)

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
<plugin>
8383
<groupId>org.jacoco</groupId>
8484
<artifactId>jacoco-maven-plugin</artifactId>
85-
<version>0.8.13</version>
85+
<version>0.8.14</version>
8686
<executions>
8787
<execution>
8888
<goals>
@@ -112,7 +112,7 @@
112112
<dependency>
113113
<groupId>com.puppycrawl.tools</groupId>
114114
<artifactId>checkstyle</artifactId>
115-
<version>12.0.0</version>
115+
<version>12.0.1</version>
116116
</dependency>
117117
</dependencies>
118118
</plugin>

src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.thealgorithms.datastructures.bloomfilter;
22

3+
import java.util.Arrays;
34
import java.util.BitSet;
45

56
/**
@@ -115,7 +116,7 @@ private static class Hash<T> {
115116
* @return the computed hash value
116117
*/
117118
public int compute(T key) {
118-
return index * asciiString(String.valueOf(key));
119+
return index * contentHash(key);
119120
}
120121

121122
/**
@@ -135,5 +136,31 @@ private int asciiString(String word) {
135136
}
136137
return sum;
137138
}
139+
140+
/**
141+
* Computes a content-based hash for arrays; falls back to ASCII-sum of String value otherwise.
142+
*/
143+
private int contentHash(Object key) {
144+
if (key instanceof int[]) {
145+
return Arrays.hashCode((int[]) key);
146+
} else if (key instanceof long[]) {
147+
return Arrays.hashCode((long[]) key);
148+
} else if (key instanceof byte[]) {
149+
return Arrays.hashCode((byte[]) key);
150+
} else if (key instanceof short[]) {
151+
return Arrays.hashCode((short[]) key);
152+
} else if (key instanceof char[]) {
153+
return Arrays.hashCode((char[]) key);
154+
} else if (key instanceof boolean[]) {
155+
return Arrays.hashCode((boolean[]) key);
156+
} else if (key instanceof float[]) {
157+
return Arrays.hashCode((float[]) key);
158+
} else if (key instanceof double[]) {
159+
return Arrays.hashCode((double[]) key);
160+
} else if (key instanceof Object[]) {
161+
return Arrays.deepHashCode((Object[]) key);
162+
}
163+
return asciiString(String.valueOf(key));
164+
}
138165
}
139166
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Objects;
8+
import java.util.PriorityQueue;
9+
import java.util.Set;
10+
11+
/**
12+
* Yen's algorithm for finding K loopless shortest paths in a directed graph with non-negative edge weights.
13+
*
14+
* <p>Input is an adjacency matrix of edge weights. A value of -1 indicates no edge.
15+
* All existing edge weights must be non-negative. Zero-weight edges are allowed.</p>
16+
*
17+
* <p>References:
18+
* - Wikipedia: Yen's algorithm (https://en.wikipedia.org/wiki/Yen%27s_algorithm)
19+
* - Dijkstra's algorithm for the base shortest path computation.</p>
20+
*/
21+
public final class YensKShortestPaths {
22+
23+
private YensKShortestPaths() {
24+
}
25+
26+
private static final int NO_EDGE = -1;
27+
private static final long INF_COST = Long.MAX_VALUE / 4;
28+
29+
/**
30+
* Compute up to k loopless shortest paths from src to dst using Yen's algorithm.
31+
*
32+
* @param weights adjacency matrix; weights[u][v] = -1 means no edge; otherwise non-negative edge weight
33+
* @param src source vertex index
34+
* @param dst destination vertex index
35+
* @param k maximum number of paths to return (k >= 1)
36+
* @return list of paths, each path is a list of vertex indices in order from src to dst
37+
* @throws IllegalArgumentException on invalid inputs (null, non-square, negatives on existing edges, bad indices, k < 1)
38+
*/
39+
public static List<List<Integer>> kShortestPaths(int[][] weights, int src, int dst, int k) {
40+
validate(weights, src, dst, k);
41+
final int n = weights.length;
42+
// Make a defensive copy to avoid mutating caller's matrix
43+
int[][] weightsCopy = new int[n][n];
44+
for (int i = 0; i < n; i++) {
45+
weightsCopy[i] = Arrays.copyOf(weights[i], n);
46+
}
47+
48+
List<Path> shortestPaths = new ArrayList<>();
49+
PriorityQueue<Path> candidates = new PriorityQueue<>(); // min-heap by cost then lexicographic nodes
50+
Set<String> seen = new HashSet<>(); // deduplicate candidate paths by node sequence key
51+
52+
Path first = dijkstra(weightsCopy, src, dst, new boolean[n]);
53+
if (first == null) {
54+
return List.of();
55+
}
56+
shortestPaths.add(first);
57+
58+
for (int kIdx = 1; kIdx < k; kIdx++) {
59+
Path lastPath = shortestPaths.get(kIdx - 1);
60+
List<Integer> lastNodes = lastPath.nodes;
61+
for (int i = 0; i < lastNodes.size() - 1; i++) {
62+
int spurNode = lastNodes.get(i);
63+
List<Integer> rootPath = lastNodes.subList(0, i + 1);
64+
65+
// Build modified graph: remove edges that would recreate same root + next edge as any A path
66+
int[][] modifiedWeights = cloneMatrix(weightsCopy);
67+
68+
for (Path p : shortestPaths) {
69+
if (startsWith(p.nodes, rootPath) && p.nodes.size() > i + 1) {
70+
int u = p.nodes.get(i);
71+
int v = p.nodes.get(i + 1);
72+
modifiedWeights[u][v] = NO_EDGE; // remove edge
73+
}
74+
}
75+
// Prevent revisiting nodes in rootPath (loopless constraint), except spurNode itself
76+
boolean[] blocked = new boolean[n];
77+
for (int j = 0; j < rootPath.size() - 1; j++) {
78+
blocked[rootPath.get(j)] = true;
79+
}
80+
81+
Path spurPath = dijkstra(modifiedWeights, spurNode, dst, blocked);
82+
if (spurPath != null) {
83+
// concatenate rootPath (excluding spurNode at end) + spurPath
84+
List<Integer> totalNodes = new ArrayList<>(rootPath);
85+
// spurPath.nodes starts with spurNode; avoid duplication
86+
for (int idx = 1; idx < spurPath.nodes.size(); idx++) {
87+
totalNodes.add(spurPath.nodes.get(idx));
88+
}
89+
long rootCost = pathCost(weightsCopy, rootPath);
90+
long totalCost = rootCost + spurPath.cost; // spurPath.cost covers from spurNode to dst
91+
Path candidate = new Path(totalNodes, totalCost);
92+
String key = candidate.key();
93+
if (seen.add(key)) {
94+
candidates.add(candidate);
95+
}
96+
}
97+
}
98+
if (candidates.isEmpty()) {
99+
break;
100+
}
101+
shortestPaths.add(candidates.poll());
102+
}
103+
104+
// Map to list of node indices for output
105+
List<List<Integer>> result = new ArrayList<>(shortestPaths.size());
106+
for (Path p : shortestPaths) {
107+
result.add(new ArrayList<>(p.nodes));
108+
}
109+
return result;
110+
}
111+
112+
private static void validate(int[][] weights, int src, int dst, int k) {
113+
if (weights == null || weights.length == 0) {
114+
throw new IllegalArgumentException("Weights matrix must not be null or empty");
115+
}
116+
int n = weights.length;
117+
for (int i = 0; i < n; i++) {
118+
if (weights[i] == null || weights[i].length != n) {
119+
throw new IllegalArgumentException("Weights matrix must be square");
120+
}
121+
for (int j = 0; j < n; j++) {
122+
int val = weights[i][j];
123+
if (val < NO_EDGE) {
124+
throw new IllegalArgumentException("Weights must be -1 (no edge) or >= 0");
125+
}
126+
}
127+
}
128+
if (src < 0 || dst < 0 || src >= n || dst >= n) {
129+
throw new IllegalArgumentException("Invalid src/dst indices");
130+
}
131+
if (k < 1) {
132+
throw new IllegalArgumentException("k must be >= 1");
133+
}
134+
}
135+
136+
private static boolean startsWith(List<Integer> list, List<Integer> prefix) {
137+
if (prefix.size() > list.size()) {
138+
return false;
139+
}
140+
for (int i = 0; i < prefix.size(); i++) {
141+
if (!Objects.equals(list.get(i), prefix.get(i))) {
142+
return false;
143+
}
144+
}
145+
return true;
146+
}
147+
148+
private static int[][] cloneMatrix(int[][] a) {
149+
int n = a.length;
150+
int[][] b = new int[n][n];
151+
for (int i = 0; i < n; i++) {
152+
b[i] = Arrays.copyOf(a[i], n);
153+
}
154+
return b;
155+
}
156+
157+
private static long pathCost(int[][] weights, List<Integer> nodes) {
158+
long cost = 0;
159+
for (int i = 0; i + 1 < nodes.size(); i++) {
160+
int u = nodes.get(i);
161+
int v = nodes.get(i + 1);
162+
int edgeCost = weights[u][v];
163+
if (edgeCost < 0) {
164+
return INF_COST; // invalid
165+
}
166+
cost += edgeCost;
167+
}
168+
return cost;
169+
}
170+
171+
private static Path dijkstra(int[][] weights, int src, int dst, boolean[] blocked) {
172+
int n = weights.length;
173+
final long inf = INF_COST;
174+
long[] dist = new long[n];
175+
int[] parent = new int[n];
176+
Arrays.fill(dist, inf);
177+
Arrays.fill(parent, -1);
178+
PriorityQueue<Node> queue = new PriorityQueue<>();
179+
if (blocked[src]) {
180+
return null;
181+
}
182+
dist[src] = 0;
183+
queue.add(new Node(src, 0));
184+
while (!queue.isEmpty()) {
185+
Node current = queue.poll();
186+
if (current.dist != dist[current.u]) {
187+
continue;
188+
}
189+
if (current.u == dst) {
190+
break;
191+
}
192+
for (int v = 0; v < n; v++) {
193+
int edgeWeight = weights[current.u][v];
194+
if (edgeWeight >= 0 && !blocked[v]) {
195+
long newDist = current.dist + edgeWeight;
196+
if (newDist < dist[v]) {
197+
dist[v] = newDist;
198+
parent[v] = current.u;
199+
queue.add(new Node(v, newDist));
200+
}
201+
}
202+
}
203+
}
204+
if (dist[dst] >= inf) {
205+
// If src==dst and not blocked, the path is trivial with cost 0
206+
if (src == dst) {
207+
List<Integer> nodes = new ArrayList<>();
208+
nodes.add(src);
209+
return new Path(nodes, 0);
210+
}
211+
return null;
212+
}
213+
// Reconstruct path
214+
List<Integer> nodes = new ArrayList<>();
215+
int cur = dst;
216+
while (cur != -1) {
217+
nodes.add(0, cur);
218+
cur = parent[cur];
219+
}
220+
return new Path(nodes, dist[dst]);
221+
}
222+
223+
private static final class Node implements Comparable<Node> {
224+
final int u;
225+
final long dist;
226+
Node(int u, long dist) {
227+
this.u = u;
228+
this.dist = dist;
229+
}
230+
public int compareTo(Node o) {
231+
return Long.compare(this.dist, o.dist);
232+
}
233+
}
234+
235+
private static final class Path implements Comparable<Path> {
236+
final List<Integer> nodes;
237+
final long cost;
238+
Path(List<Integer> nodes, long cost) {
239+
this.nodes = nodes;
240+
this.cost = cost;
241+
}
242+
String key() {
243+
return nodes.toString();
244+
}
245+
@Override
246+
public int compareTo(Path o) {
247+
int costCmp = Long.compare(this.cost, o.cost);
248+
if (costCmp != 0) {
249+
return costCmp;
250+
}
251+
// tie-break lexicographically on nodes
252+
int minLength = Math.min(this.nodes.size(), o.nodes.size());
253+
for (int i = 0; i < minLength; i++) {
254+
int aNode = this.nodes.get(i);
255+
int bNode = o.nodes.get(i);
256+
if (aNode != bNode) {
257+
return Integer.compare(aNode, bNode);
258+
}
259+
}
260+
return Integer.compare(this.nodes.size(), o.nodes.size());
261+
}
262+
}
263+
}

0 commit comments

Comments
 (0)