Pollard's Rho Algorithm 教程

[前言]

一般而言, 实现分解质因数有两种方式 :

(1). 用筛法 O(N) 预处理每个数的最小质因子 ,从而在 O(logN) 的时间内完成单次分解。 此算法的空间复杂度是 O(N) 的。

(2). 枚举 2N 中每个数 , 进行试除法。 此算法的时间复杂度为 O(N) , 而空间复杂度为 O(1)

可以看出这两种做法的时间复杂度开支都太大了。 而本文介绍的 Pollard's Rho Algorithm 可以在
O(N14+N14α(N)β+βα(N)) (α(N) 表示求最大公约数的复杂度) 的时间内 , 花费 O(1) 空间完成找出给定数 N 的一个非平凡因子 p

[前置内容]

在理解 Pollard's Rho Algorithm 的原理之前 , 首先我们需要了解以下知识 :

Miller-Rabin 素数测试 :

如何判断一个数是质数? 最朴素的方法是 : 枚举 2N 中每个数 , 判断其是否是 N 的约数。 但这样的时间复杂度达到了 O(N)。 我们有更优的做法 :

首先 , 根据费马小定理 , 若 p 是质数 , 那么对于 0<a<p , 有 ap11 (mod  p)

我们还知道若 p 是质数 , 且 x21 (mod  p) , 那么 x1 (mod  p)xp1 (mod  p) 中必有一式成立。这是因为 x21 (mod  p) 可以推出 (x1)(x+1)0 (mod  p)

那么我们就得到了一个判定方法 : 对于要判断的数 N , 选取一底数 a 满足 a<N , 判断引理 12 是否成立。 若不成立直接得到 N 是合数 , 接着反复验证引理 2 是否成立 (若指数 N 是奇数,且 aN11 (mod  N) , 就可以将 N 变为 N12)。

这样单次测试的错误率是 14 ,然而通过选取多个不同的底数 , 可以将错误率降低至 14k

具体而言 , 对于 N<264 , 选取前十二个质数作为底数即可达到 100% 的正确率。

此算法将时间复杂度提高至最劣情况下 O(log2N)

生日悖论 :

我们知道, 在中国大多数高中 , 每个班有 4050 位同学。 你的班级是否有两个有缘人是同年同月同日生呢? 这样的概率是很大的 , 由下图就可以知晓。

(此图来源为 Wikipedia)

事实上,我们可以用函数表示 N 个人生日两两不同的概率 :

N=23 时 , 存在两人生日相同的概率就达到了 50%。 这也对应了上图。

弗洛伊德判环法 :

我们来解决一个这样的问题 : 有一个伪随机数列 a 和随机函数 f , 其递推公式为 ai=fai1。这个序列存在一个循环节 , 请给出循环开始的第一个位置 μ 和环长 ϕ

解决这个问题最简单的方法是用平衡树 / 哈希表判断每个数是否在之前出现过了,这样的时间复杂度是 O((μ+ϕ)log(μ+ϕ)) 的。 并且花费了 O(μ+ϕ) 的空间。而弗洛伊德的方法是 : 用两个指针,一个指针每次走一步,另一个每次走两步, 如果两个指针指向的数对应值相同 , 那么就找到了一个或若干个循环 , 在这个基础上可以轻松得到 μϕ 的具体值。简而言之 , 就是需要找到第一个 i 满足 ai=a2i

我们分析此做法的时间复杂度 : 对于任意 i 满足 iμ ,都有 ai=ai+kϕ。 由 ai=a2i 可以推得 i+kϕ=2i , i=kϕ。 因此我们找到的 i 事实上是第一个 k 满足 kϕμ

因此此算法时间复杂度是 O(ϕ+μ) 的。 并且空间复杂度只有 O(1)

Richard P. Brent 教授对这个算法做了一些常数优化。 思路是 : 每次走 2 的次幂步 , 直到发现循环为止。 这个优化使我们可以直接找到 ϕ 的精确值。根据他本人的试验, 此做法将 Pollard's Rho Algorithm 的运行效率提高约 24%

[算法流程]

我们已经了解了上述知识 , 下面回归问题本身 , 如何快速分解质因数?

首先用 Miller-Rabin 素数测试判断 N 是否是质数。

然后 , 一个简单思路是 , 若我们能找到 N=pq (pqN 的非平凡因子) , 便可以将 pq 递归地继续分解下去。

不妨构造伪随机数列 ai=fai1 mod N , 其中 f(x)=x2+c (c 为一常数)。 而另一个数列 {anmod p} 客观存在 ,由生日悖论我们可知 {an} 的的循环节是 O(N) 级别的 , 而 {anmod p} 的循环节长度是 O(p) 级别的。

假设存在两个位置 i,j 使得 aimod p=ajmod paiaj , 取 gcd(aiaj,N) , 就得到 N 的一个非平凡因子。

因此我们要做的其实是找出 {anmod p} 的循环节 , 不妨使用弗洛伊德判环法 ,计算 gcd(xix2i,N)

gcd(xix2i,N)=1 , 那么将 i 加一
gcd(xix2i,N)=N , 说明 i2i 同时在两个数列的循环节上。 需要调整随机函数 f
否则直接返回 gcd(xix2i,N)

因此我们期望用 O(p)i 分解出 N 的一个非平凡因子。 Pollard's Rho Algorithm 算法分解一个因子的复杂度是 O(N14α(N)) 的。

事实上 , 可以继续优化

我们拟定一个参数 β,计算每 βxix2i 的乘积对 N 取模的结果 prod

gcd(prod,N)=1 ,则说明这 βxix2iN 的 gcd 均为 1 。
gcd(prod,N)1 ,那么我们就找到了 N 的一个非平凡因子 gcd(prod,N)
否则,即 gcd(prod,N)=N ,我们不知道此时我们是否需要重新进行算法,需要回到 β 次操作之前。

故算法产生一次有效的分解的期望时间复杂度被优化至了 O(N14+N14α(N)β+βα(N))

下面是 Pollard's Rho Algorithm 的一个演示图 , 相信会有助于您更好地理解。 来源为 Wikipedia

posted @   evenbao  阅读(340)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示