Pollard's Rho Algorithm 教程

[前言]

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

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

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

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

[前置内容]

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

Miller-Rabin 素数测试 :

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

首先 , 根据费马小定理 , 若 \(p\) 是质数 , 那么对于 \(0 < a < p\) , 有 \(a^{p-1}\equiv1\ (mod\ \ p)\)

我们还知道若 \(p\) 是质数 , 且 \(x^2\equiv1\ (mod\ \ p)\) , 那么 \(x\equiv1\ (mod\ \ p)\)\(x\equiv p-1\ (mod\ \ p)\) 中必有一式成立。这是因为 \(x^2\equiv1\ (mod\ \ p)\) 可以推出 \((x-1)(x+1)\equiv0\ (mod\ \ p)\)

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

这样单次测试的错误率是 \(\frac{1}{4}\) ,然而通过选取多个不同的底数 , 可以将错误率降低至 \(\frac{1}{4 ^ {k}}\)

具体而言 , 对于 \(N < 2^{64}\) , 选取前十二个质数作为底数即可达到 \(100\%\) 的正确率。

此算法将时间复杂度提高至最劣情况下 \(O(log^2N)\)

生日悖论 :

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

(此图来源为 Wikipedia)

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

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

弗洛伊德判环法 :

我们来解决一个这样的问题 : 有一个伪随机数列 \(a\) 和随机函数 \(f\) , 其递推公式为 \(a_{i} = f_{a_{i - 1}}\)。这个序列存在一个循环节 , 请给出循环开始的第一个位置 \(\mu\) 和环长 \(\phi\)

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

我们分析此做法的时间复杂度 : 对于任意 \(i\) 满足 \(i \geq \mu\) ,都有 \(a_{i} = a_{i + k\phi}\)。 由 \(a_{i} = a_{2i}\) 可以推得 \(i + k\phi = 2i\) , \(i = k\phi\)。 因此我们找到的 \(i\) 事实上是第一个 \(k\) 满足 \(k\phi \geq \mu\)

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

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

[算法流程]

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

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

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

不妨构造伪随机数列 \(a_{i} = f_{a_{i - 1}} \ mod\ N\) , 其中 \(f(x) = x ^ {2} + c\) (\(c\) 为一常数)。 而另一个数列 \(\{a_{n} mod\ p\}\) 客观存在 ,由生日悖论我们可知 \(\{a_{n}\}\) 的的循环节是 \(O(\sqrt{N})\) 级别的 , 而 \(\{a_{n} mod\ p\}\) 的循环节长度是 \(O(\sqrt{p})\) 级别的。

假设存在两个位置 \(i,j\) 使得 \(a_{i} mod \ p = a_{j} mod \ p\)\(a_{i} \neq a_{j}\) , 取 \(gcd(a_{i} - a_{j} , N)\) , 就得到 \(N\) 的一个非平凡因子。

因此我们要做的其实是找出 \(\{a_{n} mod \ p\}\) 的循环节 , 不妨使用弗洛伊德判环法 ,计算 \(gcd(x_i-x_{2i},N)\)

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

因此我们期望用 \(O(\sqrt{p})\)\(i\) 分解出 \(N\) 的一个非平凡因子。 Pollard's Rho Algorithm 算法分解一个因子的复杂度是 \(O(N^{\frac{1}{4}}*\alpha(N))\) 的。

事实上 , 可以继续优化

我们拟定一个参数 \(\beta\),计算每 \(\beta\)\(x_i−x_{2i}\) 的乘积对 \(N\) 取模的结果 \(prod\)

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

故算法产生一次有效的分解的期望时间复杂度被优化至了 \(O(N^{\frac{1}{4}}+\frac{N^{\frac{1}{4}}*\alpha(N)}{\beta}+\beta*\alpha(N))\)

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

posted @ 2022-03-25 22:38  evenbao  阅读(323)  评论(0编辑  收藏  举报