大整数分解——Pollard Rho算法

延续上一篇,这次来讲一讲大整数分解算法的应用。

要解决的问题很简单,对一个整数进行分解质因数。

首先还是效率非常低的暴力算法,相信大家都会,不多提。

和上次一样,当数达到非常大的时候,分解将变得非常困难。于是这次又带来一个提升分解速度的“非完美”算法。之所以打引号,是因为这次不完美的不是结果,而是时间效率。

Pollard Rho算法分解一个数n的过程大体上是这样子的:

1、找到一个数p,使得 p|n ,将n分解为p与n/p

2、如果p或n/p不为质数,将其带入递归上述过程

3、如果其是质数,将其记录并退出

是不是很sb。。。有人就会问了:这跟暴力分解有什么区别?好像时间复杂度还比暴力高一些。。。

所以:下面的优化才是关键。

第一个优化,使用Miller Rabin算法判定其是否为质数,这个不多提。

关键就在于接下来的这个优化。

对于一个大整数n,我们要找到一个p满足 p|n ,这显然如大海捞针。但是如果我们要找出p1、p2,使得 abs(p1p2)|n ,这看起来似乎要容易一些。实际上我们只需要找出 gcd(abs(p1p2),n)>1 的p1、p2,则其gcd值肯定为n的约数。这看起来又容易了一些。

实际上,不止容易一些,而是容易许多。根据某个玄学理论(生日悖论,详见百度,在此不赘述),这种两两比较的方式,在加入比较的数越来越多的时候,其概率会大大提升,比找一个数的概率提升快很多。

于是现在,找p的过程变成了这个样子:

1、找到一个数p1

2、通过某种玄学推导手段找出一个与p1对应的p2

3、判断 gcd(abs(p1p2),n) 是否大于1,不大于则将p2作为新的p1,重复过程,否则就找到了

怎么又是玄学?因为只有通过推导手段,才能保证不做重复判断,浪费时间。理论上的推导手段可以有很多,但实际使用中统一使用如下公式推导:

p2=(p12+c)modn

其中c为随机常数。

这个公式的好处:

1、显然推导出来的p2-p1差值基本不会相等。

2、可以证明,该推导结果会出现循环。也就是说,在出现循环之前,结果不会重复,少做了许多无用的判断。

出现循环了怎么办?换一个随机常数再搞。这就是该算法“非完美”的地方,如果人品太差那就。。。不过根据上面函数图像可知,两个随机常数产生的推导结果基本不会有重复,所以就可以放心开搞了。

最后一点,判环怎么判?floyd判圈算法搞定。(一个标记以另一个标记几倍速度走,在环上总能碰到。详见百度)

需要注意的是,之所以不能一个标记定在原地,是因为循环节不一定在开头就产生,可能走着走着才遇到循环。这条路径就类似于 ρ ,Pollard Rho算法因此得名。

最后附上代码。

typedef long long LL;
LL Pollard_Rho(LL n,LL c)
{
    LL i=1,j=2,x=rand()%(n-1)+1,y=x;//随机初始化一个基数(p1)
    while(1)
    {
        i++;
        x=(modmul(x,x,n)+c)%n;//玄学递推
        LL p=gcd((y-x+n)%n,n);
        if(p!=1&&p!=n)return p;//判断
        if(y==x)return n;//y为x的备份,相等则说明遇到圈,退出
        if(i==j)
        {
            y=x;
            j<<=1;
        }//更新y,判圈算法应用
    }
}
void find(LL n,LL c)//同上,n为待分解数,c为随机常数
{
    if(n==1)return;
    if(Miller_Rabin(n))//n为质数
    {
        //保存,根据不同题意有不同写法,在此略去
        return;
    }
    LL x=n,k=c;
    while(x==n)x=Pollard_Rho(x,c--);//当未分解成功时,换个c带入算法
    find(n/x,k);
    find(x,k);
    //递归操作
}

你可能感兴趣的:(数论)