Skip to content

Commit 5345a68

Browse files
Merge branch 'master' into remove-stars-complex-mult
2 parents 5f22138 + 9f985b2 commit 5345a68

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Arrays;
5+
import java.util.Deque;
6+
import java.util.List;
7+
8+
/**
9+
* 0-1 BFS for shortest paths on graphs with edges weighted 0 or 1.
10+
*
11+
* <p>Time Complexity: O(V + E). Space Complexity: O(V).
12+
*
13+
* <p>References:
14+
* <ul>
15+
* <li>https://cp-algorithms.com/graph/01_bfs.html</li>
16+
* </ul>
17+
*/
18+
public final class ZeroOneBfs {
19+
20+
private ZeroOneBfs() {
21+
// Utility class; do not instantiate.
22+
}
23+
24+
/**
25+
* Computes shortest distances from {@code src} in a graph whose edges have weight 0 or 1.
26+
*
27+
* @param n the number of vertices, labeled {@code 0..n-1}
28+
* @param adj adjacency list; for each vertex u, {@code adj.get(u)} is a list of pairs
29+
* {@code (v, w)} where {@code v} is a neighbor and {@code w} is 0 or 1
30+
* @param src the source vertex
31+
* @return an array of distances; {@code Integer.MAX_VALUE} denotes unreachable
32+
* @throws IllegalArgumentException if {@code n < 0}, {@code src} is out of range,
33+
* or any edge has weight other than 0 or 1
34+
*/
35+
public static int[] shortestPaths(int n, List<List<int[]>> adj, int src) {
36+
if (n < 0 || src < 0 || src >= n) {
37+
throw new IllegalArgumentException("Invalid n or src");
38+
}
39+
int[] dist = new int[n];
40+
Arrays.fill(dist, Integer.MAX_VALUE);
41+
Deque<Integer> dq = new ArrayDeque<>();
42+
43+
dist[src] = 0;
44+
dq.addFirst(src);
45+
46+
while (!dq.isEmpty()) {
47+
int u = dq.pollFirst();
48+
List<int[]> edges = adj.get(u);
49+
if (edges == null) {
50+
continue;
51+
}
52+
for (int[] e : edges) {
53+
if (e == null || e.length < 2) {
54+
continue;
55+
}
56+
int v = e[0];
57+
int w = e[1];
58+
if (v < 0 || v >= n) {
59+
continue; // ignore bad edges
60+
}
61+
if (w != 0 && w != 1) {
62+
throw new IllegalArgumentException("Edge weight must be 0 or 1");
63+
}
64+
int nd = dist[u] + w;
65+
if (nd < dist[v]) {
66+
dist[v] = nd;
67+
if (w == 0) {
68+
dq.addFirst(v);
69+
} else {
70+
dq.addLast(v);
71+
}
72+
}
73+
}
74+
}
75+
return dist;
76+
}
77+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.thealgorithms.maths;
2+
3+
import java.math.BigInteger;
4+
import java.util.Random;
5+
6+
/**
7+
* The {@code EulerPseudoprime} class implements the Euler primality test.
8+
*
9+
* It is based on Euler’s criterion:
10+
* For an odd prime number {@code n} and any integer {@code a} coprime to {@code n}:
11+
* a^((n-1)/2) ≡ (a/n) (mod n)
12+
* where (a/n) is the Jacobi symbol.
13+
*
14+
* This algorithm is a stronger probabilistic test than Fermat’s test.
15+
* It may still incorrectly identify a composite as “probably prime” (Euler pseudoprime),
16+
* but such cases are rare.
17+
*/
18+
public final class EulerPseudoprime {
19+
20+
private EulerPseudoprime() {
21+
// Private constructor to prevent instantiation.
22+
}
23+
24+
private static final Random RANDOM = new Random(1);
25+
26+
/**
27+
* Performs the Euler primality test for a given number.
28+
*
29+
* @param n number to test (must be > 2 and odd)
30+
* @param trials number of random bases to test
31+
* @return {@code true} if {@code n} passes all Euler tests (probably prime),
32+
* {@code false} if composite.
33+
*/
34+
public static boolean isProbablePrime(BigInteger n, int trials) {
35+
if (n.compareTo(BigInteger.TWO) < 0) {
36+
return false;
37+
}
38+
if (n.equals(BigInteger.TWO) || n.equals(BigInteger.valueOf(3))) {
39+
return true;
40+
}
41+
if (n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
42+
return false;
43+
}
44+
45+
for (int i = 0; i < trials; i++) {
46+
BigInteger a = uniformRandom(BigInteger.TWO, n.subtract(BigInteger.TWO));
47+
BigInteger jacobi = BigInteger.valueOf(jacobiSymbol(a, n));
48+
if (jacobi.equals(BigInteger.ZERO)) {
49+
return false;
50+
}
51+
52+
BigInteger exp = n.subtract(BigInteger.ONE).divide(BigInteger.TWO);
53+
BigInteger modExp = a.modPow(exp, n);
54+
55+
// Euler's criterion: a^((n-1)/2) ≡ (a/n) (mod n)
56+
if (!modExp.equals(jacobi.mod(n))) {
57+
return false; // definitely composite
58+
}
59+
}
60+
return true; // probably prime
61+
}
62+
63+
/**
64+
* Computes the Jacobi symbol (a/n).
65+
* Assumes n is positive and odd.
66+
*/
67+
public static int jacobiSymbol(BigInteger a, BigInteger n) {
68+
if (n.signum() <= 0 || n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
69+
throw new IllegalArgumentException("n must be positive and odd.");
70+
}
71+
72+
int result = 1;
73+
a = a.mod(n);
74+
75+
while (a.compareTo(BigInteger.ZERO) != 0) {
76+
while (a.mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
77+
a = a.divide(BigInteger.TWO);
78+
BigInteger nMod8 = n.mod(BigInteger.valueOf(8));
79+
if (nMod8.equals(BigInteger.valueOf(3)) || nMod8.equals(BigInteger.valueOf(5))) {
80+
result = -result;
81+
}
82+
}
83+
84+
BigInteger temp = a;
85+
a = n;
86+
n = temp;
87+
88+
if (a.mod(BigInteger.valueOf(4)).equals(BigInteger.valueOf(3)) && n.mod(BigInteger.valueOf(4)).equals(BigInteger.valueOf(3))) {
89+
result = -result;
90+
}
91+
92+
a = a.mod(n);
93+
}
94+
95+
return n.equals(BigInteger.ONE) ? result : 0;
96+
}
97+
98+
/**
99+
* Generates a random BigInteger between {@code min} and {@code max}, inclusive.
100+
*/
101+
private static BigInteger uniformRandom(BigInteger min, BigInteger max) {
102+
BigInteger result;
103+
do {
104+
result = new BigInteger(max.bitLength(), RANDOM);
105+
} while (result.compareTo(min) < 0 || result.compareTo(max) > 0);
106+
return result;
107+
}
108+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import org.junit.jupiter.api.Test;
9+
10+
class ZeroOneBfsTest {
11+
12+
// Helper to build adjacency list with capacity n
13+
private static List<List<int[]>> makeAdj(int n) {
14+
List<List<int[]>> adj = new ArrayList<>(n);
15+
for (int i = 0; i < n; i++) {
16+
adj.add(new ArrayList<>());
17+
}
18+
return adj;
19+
}
20+
21+
@Test
22+
void simpleLineGraph() {
23+
int n = 4;
24+
List<List<int[]>> adj = makeAdj(n);
25+
// 0 --0--> 1 --1--> 2 --0--> 3
26+
adj.get(0).add(new int[] {1, 0});
27+
adj.get(1).add(new int[] {2, 1});
28+
adj.get(2).add(new int[] {3, 0});
29+
30+
int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0);
31+
assertArrayEquals(new int[] {0, 0, 1, 1}, dist);
32+
}
33+
34+
@Test
35+
void parallelEdgesPreferZero() {
36+
int n = 3;
37+
List<List<int[]>> adj = makeAdj(n);
38+
// Two edges 0->1: weight 1 and weight 0. Algorithm should choose 0.
39+
adj.get(0).add(new int[] {1, 1});
40+
adj.get(0).add(new int[] {1, 0});
41+
adj.get(1).add(new int[] {2, 1});
42+
43+
int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0);
44+
assertArrayEquals(new int[] {0, 0, 1}, dist);
45+
}
46+
47+
@Test
48+
void unreachableNodes() {
49+
int n = 3;
50+
List<List<int[]>> adj = makeAdj(n);
51+
adj.get(0).add(new int[] {1, 0});
52+
int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0);
53+
// node 2 unreachable -> Integer.MAX_VALUE
54+
assertArrayEquals(new int[] {0, 0, Integer.MAX_VALUE}, dist);
55+
}
56+
57+
@Test
58+
void invalidArgs() {
59+
int n = 2;
60+
List<List<int[]>> adj = makeAdj(n);
61+
// invalid weight
62+
adj.get(0).add(new int[] {1, 2});
63+
assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, 0));
64+
// invalid src
65+
assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, -1));
66+
assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, 2));
67+
}
68+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.thealgorithms.maths;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
import static org.mockito.ArgumentMatchers.any;
7+
8+
import java.math.BigInteger;
9+
import org.junit.jupiter.api.Test;
10+
import org.mockito.MockedStatic;
11+
import org.mockito.Mockito;
12+
13+
class EulerPseudoprimeTest {
14+
15+
@Test
16+
void testPrimeNumbers() {
17+
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(7), 5));
18+
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(13), 5));
19+
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(101), 5));
20+
}
21+
22+
@Test
23+
void testCompositeNumbers() {
24+
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(9), 5));
25+
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(21), 5));
26+
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(221), 5));
27+
}
28+
29+
@Test
30+
void testEvenNumbers() {
31+
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(4), 5));
32+
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(100), 5));
33+
}
34+
35+
@Test
36+
void testEdgeCases() {
37+
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(0), 5));
38+
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(1), 5));
39+
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(2), 5));
40+
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(3), 5));
41+
}
42+
43+
@Test
44+
void testIsProbablePrimeWhenJacobiSymbolIsZero() {
45+
try (MockedStatic<EulerPseudoprime> mockedPrimality = Mockito.mockStatic(EulerPseudoprime.class, Mockito.CALLS_REAL_METHODS)) {
46+
47+
// Mock jacobiSymbol to return 0 to test the branch
48+
mockedPrimality.when(() -> EulerPseudoprime.jacobiSymbol(any(BigInteger.class), any(BigInteger.class))).thenReturn(0);
49+
50+
boolean result = EulerPseudoprime.isProbablePrime(BigInteger.valueOf(15), 1);
51+
52+
assertFalse(result);
53+
}
54+
}
55+
56+
@Test
57+
void testJacobiSymbolThrowsForEvenOrNonPositiveN() throws Exception {
58+
var method = EulerPseudoprime.class.getDeclaredMethod("jacobiSymbol", BigInteger.class, BigInteger.class);
59+
60+
// Helper lambda to unwrap InvocationTargetException
61+
Runnable invokeJacobi = () -> {
62+
try {
63+
method.invoke(null, BigInteger.valueOf(2), BigInteger.valueOf(8));
64+
} catch (Exception e) {
65+
// unwrap
66+
Throwable cause = e.getCause();
67+
if (cause instanceof IllegalArgumentException) {
68+
throw (IllegalArgumentException) cause;
69+
} else {
70+
throw new RuntimeException(e);
71+
}
72+
}
73+
};
74+
75+
// Now check that it actually throws
76+
assertThrows(IllegalArgumentException.class, invokeJacobi::run);
77+
78+
// Another case: non-positive n
79+
Runnable invokeJacobi2 = () -> {
80+
try {
81+
method.invoke(null, BigInteger.valueOf(5), BigInteger.valueOf(-3));
82+
} catch (Exception e) {
83+
Throwable cause = e.getCause();
84+
if (cause instanceof IllegalArgumentException) {
85+
throw (IllegalArgumentException) cause;
86+
} else {
87+
throw new RuntimeException(e);
88+
}
89+
}
90+
};
91+
assertThrows(IllegalArgumentException.class, invokeJacobi2::run);
92+
}
93+
}

0 commit comments

Comments
 (0)