|
| 1 | +/** |
| 2 | + * Prime factorization using Pollard's rho algorithm with Miller-Rabin primality testing. |
| 3 | + * |
| 4 | + * <p>Miller-Rabin with the deterministic witness set {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37} |
| 5 | + * is guaranteed correct for all n < 3.317 × 10^24, which covers the full range of Java's long. |
| 6 | + * |
| 7 | + * <p>Time: O(n^(1/4)) expected per factor found |
| 8 | + * |
| 9 | + * @author William Fiset, william.alexandre.fiset@gmail.com |
| 10 | + */ |
1 | 11 | package com.williamfiset.algorithms.math; |
2 | 12 |
|
3 | 13 | import java.util.ArrayList; |
4 | | -import java.util.PriorityQueue; |
| 14 | +import java.util.List; |
5 | 15 |
|
6 | 16 | public class PrimeFactorization { |
7 | 17 |
|
8 | | - public static ArrayList<Long> primeFactorization(long n) { |
9 | | - ArrayList<Long> factors = new ArrayList<>(); |
10 | | - if (n <= 0) throw new IllegalArgumentException(); |
11 | | - else if (n == 1) return factors; |
12 | | - PriorityQueue<Long> divisorQueue = new PriorityQueue<>(); |
13 | | - divisorQueue.add(n); |
14 | | - while (!divisorQueue.isEmpty()) { |
15 | | - long divisor = divisorQueue.remove(); |
16 | | - if (isPrime(divisor)) { |
17 | | - factors.add(divisor); |
18 | | - continue; |
19 | | - } |
20 | | - long next_divisor = pollardRho(divisor); |
21 | | - if (next_divisor == divisor) { |
22 | | - divisorQueue.add(divisor); |
23 | | - } else { |
24 | | - divisorQueue.add(next_divisor); |
25 | | - divisorQueue.add(divisor / next_divisor); |
26 | | - } |
27 | | - } |
| 18 | + /** |
| 19 | + * Returns the prime factorization of n. The returned factors are not necessarily sorted. |
| 20 | + * |
| 21 | + * @throws IllegalArgumentException if n <= 0. |
| 22 | + */ |
| 23 | + public static List<Long> primeFactorization(long n) { |
| 24 | + if (n <= 0) |
| 25 | + throw new IllegalArgumentException(); |
| 26 | + |
| 27 | + List<Long> factors = new ArrayList<>(); |
| 28 | + factor(n, factors); |
28 | 29 | return factors; |
29 | 30 | } |
30 | 31 |
|
| 32 | + private static void factor(long n, List<Long> factors) { |
| 33 | + if (n == 1) |
| 34 | + return; |
| 35 | + if (isPrime(n)) { |
| 36 | + factors.add(n); |
| 37 | + return; |
| 38 | + } |
| 39 | + long d = pollardRho(n); |
| 40 | + factor(d, factors); |
| 41 | + factor(n / d, factors); |
| 42 | + } |
| 43 | + |
31 | 44 | private static long pollardRho(long n) { |
32 | | - if (n % 2 == 0) return 2; |
| 45 | + if (n % 2 == 0) |
| 46 | + return 2; |
33 | 47 | long x = 2 + (long) (999999 * Math.random()); |
34 | 48 | long c = 2 + (long) (999999 * Math.random()); |
35 | 49 | long y = x; |
36 | 50 | long d = 1; |
37 | 51 | while (d == 1) { |
38 | | - x = (x * x + c) % n; |
39 | | - y = (y * y + c) % n; |
40 | | - y = (y * y + c) % n; |
| 52 | + x = mulMod(x, x, n) + c; |
| 53 | + if (x >= n) |
| 54 | + x -= n; |
| 55 | + y = mulMod(y, y, n) + c; |
| 56 | + if (y >= n) |
| 57 | + y -= n; |
| 58 | + y = mulMod(y, y, n) + c; |
| 59 | + if (y >= n) |
| 60 | + y -= n; |
41 | 61 | d = gcd(Math.abs(x - y), n); |
42 | | - if (d == n) break; |
| 62 | + if (d == n) |
| 63 | + break; |
43 | 64 | } |
44 | 65 | return d; |
45 | 66 | } |
46 | 67 |
|
47 | | - private static long gcd(long a, long b) { |
48 | | - return b == 0 ? a : gcd(b, a % b); |
49 | | - } |
| 68 | + /** |
| 69 | + * Deterministic Miller-Rabin primality test, correct for all long values. Uses 12 witnesses that |
| 70 | + * guarantee correctness for n < 3.317 × 10^24. |
| 71 | + */ |
| 72 | + private static boolean isPrime(long n) { |
| 73 | + if (n < 2) |
| 74 | + return false; |
| 75 | + if (n < 4) |
| 76 | + return true; |
| 77 | + if (n % 2 == 0 || n % 3 == 0) |
| 78 | + return false; |
| 79 | + |
| 80 | + // Write n-1 as 2^r * d |
| 81 | + long d = n - 1; |
| 82 | + int r = Long.numberOfTrailingZeros(d); |
| 83 | + d >>= r; |
50 | 84 |
|
51 | | - private static boolean isPrime(final long n) { |
52 | | - if (n < 2) return false; |
53 | | - if (n == 2 || n == 3) return true; |
54 | | - if (n % 2 == 0 || n % 3 == 0) return false; |
55 | | - long limit = (long) Math.sqrt(n); |
56 | | - for (long i = 5; i <= limit; i += 6) if (n % i == 0 || n % (i + 2) == 0) return false; |
| 85 | + for (long a : new long[]{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}) { |
| 86 | + if (a >= n) |
| 87 | + continue; |
| 88 | + long x = powMod(a, d, n); |
| 89 | + if (x == 1 || x == n - 1) |
| 90 | + continue; |
| 91 | + boolean composite = true; |
| 92 | + for (int i = 0; i < r - 1; i++) { |
| 93 | + x = mulMod(x, x, n); |
| 94 | + if (x == n - 1) { |
| 95 | + composite = false; |
| 96 | + break; |
| 97 | + } |
| 98 | + } |
| 99 | + if (composite) |
| 100 | + return false; |
| 101 | + } |
57 | 102 | return true; |
58 | 103 | } |
59 | 104 |
|
| 105 | + /** Modular exponentiation: (base^exp) % mod, using mulMod to avoid overflow. */ |
| 106 | + private static long powMod(long base, long exp, long mod) { |
| 107 | + long result = 1; |
| 108 | + base %= mod; |
| 109 | + while (exp > 0) { |
| 110 | + if ((exp & 1) == 1) |
| 111 | + result = mulMod(result, base, mod); |
| 112 | + exp >>= 1; |
| 113 | + base = mulMod(base, base, mod); |
| 114 | + } |
| 115 | + return result; |
| 116 | + } |
| 117 | + |
| 118 | + /** Overflow-safe modular multiplication: (a * b) % mod. */ |
| 119 | + private static long mulMod(long a, long b, long mod) { |
| 120 | + return java.math.BigInteger.valueOf(a) |
| 121 | + .multiply(java.math.BigInteger.valueOf(b)) |
| 122 | + .mod(java.math.BigInteger.valueOf(mod)) |
| 123 | + .longValue(); |
| 124 | + } |
| 125 | + |
| 126 | + private static long gcd(long a, long b) { |
| 127 | + return b == 0 ? a : gcd(b, a % b); |
| 128 | + } |
| 129 | + |
60 | 130 | public static void main(String[] args) { |
61 | | - System.out.println(primeFactorization(7)); // [7] |
62 | | - System.out.println(primeFactorization(100)); // [2,2,5,5] |
63 | | - System.out.println(primeFactorization(666)); // [2,3,3,37] |
64 | | - System.out.println(primeFactorization(872342345)); // [5, 7, 7, 67, 19, 2797] |
| 131 | + System.out.println(primeFactorization(7)); // [7] |
| 132 | + System.out.println(primeFactorization(100)); // [2, 2, 5, 5] |
| 133 | + System.out.println(primeFactorization(666)); // [2, 3, 3, 37] |
| 134 | + System.out.println(primeFactorization(872342345)); // [5, 7, 7, 19, 67, 2797] |
65 | 135 | } |
66 | 136 | } |
0 commit comments