算法概述
Pollard-Rho算法是一种用于大整数因数分解的随机化算法,由John Pollard于1975年提出。其核心思想基于生日悖论:在有限集合中随机选取元素,碰撞发生的期望次数远小于集合大小。算法通过构造一个伪随机序列,利用Floyd判圈算法检测序列中的循环,从而找到大合数的一个非平凡因子。
Pollard-Rho特别适用于分解具有小因子的合数,时间复杂度约为O(n^{1/4}),远优于试除法的O(√n)。该算法是RSA密码分析、数论研究和现代密码学测试中的重要工具。
核心原理
生日悖论与碰撞检测
假设有一个函数f(x)在模n下生成伪随机序列。若n有一个因子p,则序列在模p下会比模n下更快进入循环。当x_i ≡ x_j (mod p)但x_i ≢ x_j (mod n)时,gcd(|x_i – x_j|, n)将给出n的一个非平凡因子。
Floyd判圈算法
使用两个指针(龟和兔),慢指针每次走一步,快指针每次走两步。若存在循环,两指针必在环内相遇。设序列为x_{i+1} = f(x_i),检测条件为:
– 龟:x = f(x)
– 兔:y = f(f(y))
– 计算d = gcd(|x – y|, n)
多项式递推函数
通常选择f(x) = (x² + c) mod n,其中c为随机常数。若失败,更换c值重试。
Java完整实现
import java.math.BigInteger;
import java.security.SecureRandom;
/**
* Pollard-Rho大整数因数分解算法
* 基于生日悖论和Floyd判圈算法
*/
public class PollardRho {
private static final SecureRandom random = new SecureRandom();
/**
* 使用Miller-Rabin进行素性测试
* @param n 待测试的大整数
* @param certainty 测试轮数,越高越准确
* @return true表示n可能是素数,false表示n是合数
*/
public static boolean isProbablePrime(BigInteger n, int certainty) {
return n.isProbablePrime(certainty);
}
/**
* Pollard-Rho核心算法
* 寻找n的一个非平凡因子
* @param n 待分解的合数
* @return n的一个因子(可能为n本身表示失败)
*/
public static BigInteger pollardRho(BigInteger n) {
// 处理偶数
if (n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
return BigInteger.TWO;
}
// 随机选择起始值和多项式常数c
BigInteger c = new BigInteger(n.bitLength(), random).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE);
BigInteger x = new BigInteger(n.bitLength(), random).mod(n);
BigInteger y = x;
BigInteger d = BigInteger.ONE;
// Floyd判圈:x走一步,y走两步
while (d.equals(BigInteger.ONE)) {
// 龟:x = f(x) = (x^2 + c) mod n
x = x.multiply(x).mod(n).add(c).mod(n);
// 兔:y = f(f(y))
y = y.multiply(y).mod(n).add(c).mod(n);
y = y.multiply(y).mod(n).add(c).mod(n);
// 计算gcd(|x - y|, n)
d = x.subtract(y).abs().gcd(n);
// 若d == n,说明本次参数选择失败,需要重新开始
if (d.equals(n)) {
return pollardRho(n);
}
}
return d;
}
/**
* 递归分解大整数
* 将n分解为素因子的映射(因子->指数)
* @param n 待分解的整数
* @param factors 存储因子的映射
*/
public static void factor(BigInteger n, java.util.Map<BigInteger, Integer> factors) {
// 基础情况:n <= 1
if (n.compareTo(BigInteger.ONE) <= 0) {
return;
}
// 若n是素数,直接记录
if (isProbablePrime(n, 20)) {
factors.put(n, factors.getOrDefault(n, 0) + 1);
return;
}
// 使用Pollard-Rho找到一个因子
BigInteger divisor = pollardRho(n);
// 递归分解两个部分
factor(divisor, factors);
factor(n.divide(divisor), factors);
}
/**
* 对外接口:分解整数并返回因子列表
* @param n 待分解的大整数
* @return 素因子列表(含重复)
*/
public static java.util.List<BigInteger> factorize(BigInteger n) {
java.util.Map<BigInteger, Integer> factorMap = new java.util.HashMap<>();
factor(n, factorMap);
java.util.List<BigInteger> result = new java.util.ArrayList<>();
for (java.util.Map.Entry<BigInteger, Integer> entry : factorMap.entrySet()) {
for (int i = 0; i < entry.getValue(); i++) {
result.add(entry.getKey());
}
}
java.util.Collections.sort(result);
return result;
}
/**
* 主程序:测试Pollard-Rho算法
*/
public static void main(String[] args) {
// 测试用例:已知因子的大合数
BigInteger[] testCases = {
new BigInteger("10403"), // 101 * 103
new BigInteger("1000003"), // 大素数
new BigInteger("9007199254740991"), // 2^53 - 1 = 6361 * 69431 * 20394401
new BigInteger("323"), // 17 * 19
new BigInteger("8051"), // 83 * 97
};
System.out.println("===== Pollard-Rho大整数因数分解 =====\n");
for (BigInteger n : testCases) {
System.out.println("待分解数字: " + n);
long startTime = System.currentTimeMillis();
if (isProbablePrime(n, 20)) {
System.out.println("结果: " + n + " 是素数");
} else {
java.util.List<BigInteger> factors = factorize(n);
System.out.println("素因子分解: " + factors);
// 验证分解结果
BigInteger product = BigInteger.ONE;
for (BigInteger f : factors) {
product = product.multiply(f);
}
System.out.println("验证乘积: " + product + " (正确: " + product.equals(n) + ")");
}
long endTime = System.currentTimeMillis();
System.out.println("耗时: " + (endTime - startTime) + "ms\n");
}
// 性能测试:分解一个64位合数
System.out.println("===== 性能测试 =====");
BigInteger largeComposite = new BigInteger("18446744073709551617"); // 大合数
System.out.println("分解大合数: " + largeComposite);
long start = System.currentTimeMillis();
java.util.List<BigInteger> largeFactors = factorize(largeComposite);
long end = System.currentTimeMillis();
System.out.println("因子: " + largeFactors);
System.out.println("总耗时: " + (end - start) + "ms");
}
}
算法优化策略
Brent判圈优化
Floyd判圈存在一个问题:龟兔间距可能很大,导致大量不必要的gcd计算。Brent优化记录兔子走过的步数,每次2的幂次步时重置乌龟位置,可减少约30%的模运算。
public static BigInteger pollardRhoBrent(BigInteger n) {
if (n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) return BigInteger.TWO;
BigInteger y = new BigInteger(n.bitLength(), random).mod(n);
BigInteger c = new BigInteger(n.bitLength(), random).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE);
BigInteger m = new BigInteger("100"); // 块大小
BigInteger d = BigInteger.ONE;
BigInteger r = BigInteger.ONE;
BigInteger q = BigInteger.ONE;
while (d.equals(BigInteger.ONE)) {
BigInteger x = y;
for (BigInteger i = BigInteger.ZERO; i.compareTo(r) < 0; i = i.add(BigInteger.ONE)) {
y = y.multiply(y).mod(n).add(c).mod(n);
}
BigInteger k = BigInteger.ZERO;
while (k.compareTo(r) < 0 && d.equals(BigInteger.ONE)) {
BigInteger ys = y;
BigInteger maxJ = m.min(r.subtract(k));
for (BigInteger j = BigInteger.ZERO; j.compareTo(maxJ) < 0; j = j.add(BigInteger.ONE)) {
y = y.multiply(y).mod(n).add(c).mod(n);
q = q.multiply(x.subtract(y).abs()).mod(n);
}
d = q.gcd(n);
k = k.add(m);
}
r = r.multiply(BigInteger.TWO);
}
if (d.equals(n)) return pollardRhoBrent(n);
return d;
}
复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n^{1/4}) | 期望复杂度,基于生日悖论 |
| 空间复杂度 | O(1) | 仅需常数额外空间 |
| 成功率 | ~100% | 对具有小因子的合数几乎必然成功 |
| 最优场景 | – | n含有远小于√n的因子p时效率最高 |
应用场景
- 密码学分析:测试RSA密钥强度,分解弱模数
- 数论研究:研究整数的因子结构
- 算法竞赛:处理大整数分解问题
- 随机性测试:检验伪随机数生成器质量
总结
Pollard-Rho算法通过伪随机序列碰撞和Floyd判圈,巧妙地利用生日悖论实现了高效的大整数因数分解。其Java实现展示了BigInteger类在大数运算中的强大能力,以及递归分治在数论问题中的应用。理解此算法有助于深入掌握随机化算法设计和数论基础知识。