因式分解

Published: by Creative Commons Licence

  • Tags:

Pollard-Rho大整数分解

对于任意正整数n,由算术基本定理可知,n=ki=1pcii,其中pi为互不相同的素数。

而因式分解是指,将一个合数分解为两个非平凡因子的乘积,比如6=23

如何对合数n进行因式分解呢?

最简单的方式是枚举它可能的因子,由于n的最小因子一定在2n之间,因此可以在时间复杂度O(n)内完成。

Pollard's Rho算法在1975年由John Pollard发明,对于分解合数n,它的期望时间复杂度仅为O(n14)

首先我们考虑建立一组序列X=(x1,x2,),其中xi+1=x2i+c(modn)。由于是处于n的剩余类环中,因此序列中实际包含的不同值最多为n个。序列的图像应该如下:

https://upload.wikimedia.org/wikipedia/commons/4/47/Pollard_rho_cycle.jpg

由于图像酷似一个ρ,因此算法得名rho。

由生日悖论可知,在一年有d天的情况下,当班级里有d个学生的时候,学生中期望至少有一对学生的生日相同,即发生碰撞。将生日悖论用在我们的序列中,可知我们序列中的不同值的数量约莫为O(n)

mn的最小非平凡因子,易知mn。我们记yi=xi(modm),可以推导:

yi+1=xi+1(modm)=(x2i+c(modn))(modm)=x2i+c(modm)=(xi(modm))2+c(modm)=y2i+c(modm)

因此我们得到了新的序列Y=(y1,y2,),同样根据生日悖论,可知序列中不同值的数量约为O(m)O(4n)

由于第一次碰撞等概率发生在序列上,因此序列X中的环的入口期望为第n/2个元素,而Y中的环的入口期望为第m/2个元素。因此X中环的长度Cx期望为n/2,同理Y中的环的长度Cy期望为m/2。由于环的大小期望不同,那么就应该存在一对数i,j,满足xixjyi=yj。这意味着n|xixj|m|xixj|。因此我们可以通过gcd(n,|xixj|)获得一个n的非平凡因子。

那么我们该如何获得ij呢。有两种方法,一个是Floyd判圈算法,另外一种是倍增法。当Floyd判圈法记录两个下标i,j,每次循环i=i+1j=j+2。当Cy(ji)时,我们就能找到对应的ij。而倍增法则通过枚举可能的Y序列中环上的某个值和环的长度上限实现,首先猜想y1落在环上且环的长度不大于1,如果第i次猜测失败,下一次就猜测y2i落在环上且环的长度不大于2i。两种算法都可以在O(m)次循环内完成。

如果你挑选的x1c不好的话,可能会导致最终没法找到n的任意因子。那么你可以在合适的时机,用新的随机数替代x1c重新进行启发式查找。

注意,这个算法是建立在n是合数的前提下的,如果n是素数,那么这个算法就永远无法找到因子而陷入死循环中。所以在执行Pollard's Rho算法之前,需要先测试n是否是合数,这里可以采用随机算法Miller Rabin进行,这里不过多讲。

并且这里O(m)是指的是循环次数,而不是时间复杂度,由于每次循环我们都会调用gcd,因此真实的时间复杂度为O(mlog2n)

int pollard_rho(int x, int c, int n)
{
    int xi = x;
    int xj = x;
    int j = 2;
    int i = 1;
    while(i < n)
    {
        i++;
        xi = (xi * xi + c) % n;
        int g = gcd(n, abs(xi - xj));
        if(g != 1 && g != n)
        {
            return g;
        }
        if(i == j)
        {
            j = j << 1;
            xj = xi;
        }
    }
    return -1;
}

上面的是倍增法的代码。