【原创】【数论】质数判断方法汇总及证明(上->费马素性检测与卡迈克尔数)

质数


一、定义:

对于一个整数p,除了1和p之外没有别的整因数的整数,称为质数。

若p为质数,则除p=1*p外没有别的分解方式。


二、性质:

≤n的质数粗略的有n/ln(n)个。(非常粗略,误差较大,但对于开数组有帮助)


三、判断:

如何判断一个整数p是否为质数?

①定义法:

既然除了1之外没有别的因数,那么我们只需要枚举从2到p-1,如果都不是p的因子,那么p就为质数。

详见代码:

bool prime(int p)
{
	for(int i=2;i<=p-1;i++)
		if(i%p==0) return false;
	return true;
}

②改良定义法

如果说p是一个偶数,则p=1*p=2*(p/2)。

可以发现,p的因数总是一对一对出现的。

如果p不是质数,那么它至少有两对因数。

设第二对中,较小的为a,较大的为b。

那么我们查到a后,函数return true,不会再往后查,根本查不到b。此时,函数只需要循环a次。

如果p是质数,那么就会循环p-2次,都不会return true。

综上,函数只有前a次循环可以出结果。

既然只有前a次可以出结果,那么只循环a次是不是就可以了?

到底循环几次呢?

我们只需要取a的最大值作为循环上限即可。

因为a≤b,a*b=p。所以a最大时,a=b,则a*a=p。所以a(max)=sqrt(p)。(p的算术平方根)

详见代码:

bool prime(int p)
{
	int k=sqrt(p);//关于sqrt(p):取p的算术平方根,需调用cmath或math.h头文件。
	for(int i=2;i<=k;i++)//关于k:不用变量储存,每次循环都需要重新计算一次sqrt,白白浪费时间。
	//关于≤:如果sqrt(p)为整数,则p一定不为质数,如9。对于9,i必须等于sqrt(9)即3才可以判断。
	//对于质数p,sqrt(p)不是整数,不用担心i等于sqrt(p)。
		if(p%i==0) return false;
	return true;
}

当然,如果不想再开一个头文件,不想用sqrt,你也可以这样:

bool prime(int p)
{
	for(int i=2;i*i<=p;i++)
		if(p%i==0) return false;
	return true;
}



③筛质数法:

如果要求你找出从1~n中所有的质数,你会怎么做?如果采用②法,时间复杂度为O(n*sqrt(n))。当n较大的时候明显超时。怎么办呢?


对于一个质数p,

p*2,p*3,p*4,......,p*k(k为满足p*k≤n的最小整数)都是合数。

那么,我们是不是只需要找出n中的所有合数,剩下的就是质数了呢?


只需要证明:在≤n的范围内,所有的合数都会被筛到;所有的质数都不会被筛到;所有作为p*2,p*3,......p*k中的p都是质数,即可。


ⅰ)所有的合数都会被筛到:

    设q满足2≤q≤n且q为合数。

    q可以质因数分解为:q=p1^z1*p2^z2*p3^z3*......*pc^zc(p为质数,z为p的次数)。

    ∴q=p1*[p1^(z1-1)*p2^z2*......*pc^zc]。

    设p1^(z1-1)*p2^z2*......*pc^zc=r;

    则q=p1*r;

    ∵q为合数;

    ∴q≠p1;

    ∴r>1。

    ∵q≤n,即p1*r≤n;

    ∴r≤k。(k为上文所设的满足p*k≤n的最大整数)

    ∵对于所有1<i≤k,p*i都会被筛到;

    ∴所有的合数都会被筛到。


ⅱ)所有的质数都不会被筛到:

    ∵所有p*2,p*3,......,p*k都是合数;

    又∵只会筛到p*2,p*3,......,p*k;

    ∴所有的质数都不会被筛到


ⅲ)所有作为p*2,p*3,......p*k中的p都是质数:

    p作为p*2,p*3,......,p*k的条件为:p没有被筛到。

    ∵所有的合数都被筛到了,而剩下的没筛到的都是质数;

    ∴题意得证。


Q.E.D.


此代码的时间复杂度介于O(n)和O(n*log(n)),因为每个合数都会被它所有的质因子筛一次。

详见代码:

bool vis[10000000];
bool prime(int n)
{
	memset(vis,0,sizeof vis);//将数组vis的所有元素清零,需调用cstring头文件
	for(int i=2;i*i<=n;i++)
	{
		if(!vis[i])
		{
			for(int j=i;i*j<=n;j++)
//关于从i*i开始循环:如5*2=10被2*5筛,5*3被3*5筛,5*4被2*10筛,若i*j中,j<i,那么i*j一定已经被筛过了,节省重复计算的时间
				vis[i*j]=1;
		}
	}
}



④改良筛质数法:

上文提到过,每个合数会被它的每个质因数筛一次。如14=2*7=7*2,它就会被筛两次;60=2*30=3*20=5*12,会被筛三次。代码肯定会大大浪费时间。

能不能让每个合数只被筛一次呢?

详见代码:

int prime[1000000];
bool vis[10000000];
void find(int n)
{
	int cnt=0;
	for(int i=2;i<=n;i++)
	{
		if(vis[i]==0) prime[++cnt]=i;
		for(int j=1;j<=cnt and prime[j]*i<=n;j++)
		{
			vis[prime[j]*i]=1;
			if(i%prime[j]==0) break;
		}
	}
}


 

这个代码可以让每个合数只被它最小的质因子筛到一次。

主要实现是靠if(i%prime[j]==0)break;这一条语句。

举个例子。

第一个质数是2,筛走了4。

筛到3的时候,筛走2,9。

筛到4的时候,它已经是2的倍数了,所以只筛走8,就break。

筛到8,就筛16…………

这样就可以筛走所有合数了。


⑤费马素性检测


根据费马小定理,

对于质数p和任何a,p互质,都有:

 a(p-1)≡1(mod p)

ap≡a(mod p)

我们任选一个a,如果a不满足上式,我们可以断言说p不是质数。

如果我们选了很多个a,都满足这个式子,那么它有很大的几率是质数。

这也被称为费马素性检测。再加上快速幂,我们就可以得到一个效率非常高的算法。

基于这个思路,我们可以写出程序:

long long Speed(long long a,long long b,long long c)
{
    long long ans=1;
    a%=c;
    while(b)    
    {    
        if(b&1) ans=(ans*a)%c;
        b>>=1; a*=a; a%=c;
    }    
    return ans;    
}
bool Fermat_Check(int p)//0->Pass 1->didn't Pass
{
    for(int T=1;T<=50;T++)
    {
        int x=rand()%(p-3)+2;
        long long R=Speed(x,p,p);
        if(R%p!=x) return 1;
    }
    return 0;
}


但是,美中不足的是,有一些数,它们通过了检测,但是它们还是合数,这样的数被称为“卡迈克尔数”,或者“卡米尔数”。

下列给出了一个一定范围内求卡迈克尔数的算法:

#include<algorithm>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
const int MAXN=102017;
bool Prime[MAXN],Fermat[MAXN],Carmichael[MAXN];
int p[10000],cnt;
void Search_P()
{
    for(int i=2;i<=MAXN;i++)
    {
        if(!Prime[i]) p[++cnt]=i;
        for(int k=1;k<=cnt and i*p[k]<=MAXN;k++)
        {
            Prime[i*p[k]]=1;
            if(i%p[k]==0) break;
        }
    }
}
long long Speed(long long a,long long b,long long c)
{
    long long ans=1;
    a%=c;
    while(b)    
    {    
        if(b&1) ans=(ans*a)%c;
        b>>=1; a*=a; a%=c;
    }    
    return ans;    
}
bool Fermat_Check(int p)//0->Pass 1->didn't Pass
{
    for(int T=1;T<=50;T++)
    {
        int x=rand()%(p-3)+2;
        long long R=Speed(x,p,p);
        if(R%p!=x) return 1;
    }
    return 0;
}
void Search_F()
{
    for(int i=1;i<=MAXN;i++)
    {
        if(Prime[i]) Fermat[i]=Fermat_Check(i);
        else Fermat[i]=0;
        if(Fermat[i]!=Prime[i]) Carmichael[i]=0;
        else Carmichael[i]=1;
    }
}
int main()
{
    Search_P();
    Search_F();
    int crr;
    while(scanf("%d",&crr)!=EOF)
    {
        if(crr==0) break;
        if(!Carmichael[crr] and crr!=1) printf("The number %d is a Carmichael number.\n",crr);
        else printf("%d is normal.\n",crr);
    }
}


根据程序可以求得一亿以内的所有卡迈克尔数:

561 1105 1729 2465 2821 6601 8911 10585 15841 29341 41041 46657 52633 62745 63973 75361 101101 115921 126217 162401 172081 188461 252601 278545 294409 314821 334153 340561 399001 410041 449065 488881 512461 530881 552721 656601 658801 670033 748657 825265 838201 852841 997633 1024651 1033669 1050985 1082809 1152271 1193221 1461241 1569457 1615681 1773289 1857241 1909001 2100901 2113921 2433601 2455921 2508013 2531845 2628073 2704801 3057601 3146221 3224065 3581761 3664585 3828001 4335241 4463641 4767841 4903921 4909177 5031181 5049001 5148001 5310721 5444489 5481451 5632705 5968873 6049681 6054985 6189121 6313681 6733693 6840001 6868261 7207201 7519441 7995169 8134561 8341201 8355841 8719309 8719921 8830801 8927101 9439201 9494101 9582145 9585541 9613297 9890881 10024561 10267951 10402561 10606681 10837321 10877581 11119105 11205601 11921001 11972017 12261061 12262321 12490201 12945745 13187665 13696033 13992265 14469841 14676481 14913991 15247621 15403285 15829633 15888313 16046641 16778881 17098369 17236801 17316001 17586361 17812081 18162001 18307381 18900973 19384289 19683001 20964961 21584305 22665505 23382529 25603201 26280073 26474581 26719701 26921089 26932081 27062101 27336673 27402481 28787185 29020321 29111881 31146661 31405501 31692805 32914441 33302401 33596641 34196401 34657141 34901461 35571601 35703361 36121345 36765901 37167361 37280881 37354465 37964809 38151361 38624041 38637361 39353665 40160737 40280065 40430401 40622401 40917241 41298985 41341321 41471521 42490801 43286881 43331401 43584481 43620409 44238481 45318561 45877861 45890209 46483633 47006785 48321001 48628801 49333201 50201089 53245921 53711113 54767881 55462177 56052361 58489201 60112885 60957361 62756641 64377991 64774081 65037817 65241793 67371265 67653433 67902031 67994641 68154001 69331969 70561921 72108421 72286501 74165065 75151441 75681541 75765313 76595761 77826001 78091201 78120001 79411201 79624621 80282161 80927821 81638401 81926461 82929001 83099521 83966401 84311569 84350561 84417985 87318001 88689601 90698401 92625121 93030145 93614521 93869665 94536001 96895441 99036001 99830641 99861985 


(你可以Copy去打表)


费马素性检验算法、Solovay-Stassen素性检验算法和Miller-Rabin素性检验算法都是用于判断一个是否为素的算法,下面是它们的区别和联系: 1.费马素性检验算法 费马素性检验算法是一种基于费马小定理的素性测试算法。它的原理是:如果p是一个素,a是小于p的正整,则a^(p-1) mod p = 1;如果p不是素,那么对于任意小于p的正整a,a^(p-1) mod p != 1。因此,我们可以在随机选择的a值下,使用快速幂算法来计算a^(p-1) mod p的值,如果结果不等于1,则p一定不是素。 缺点:费马素性检验算法存在漏报的情况,即有时候会将合误判为素。 2.Solovay-Stassen素性检验算法 Solovay-Stassen素性检验算法是一种基于欧拉准则的素性测试算法。它的原理是:如果p是一个素,a是小于p的正整,则a^((p-1)/2) mod p = +-1;如果p不是素,那么对于任意小于p的正整a,a^((p-1)/2) mod p != +-1。因此,我们可以在随机选择的a值下,使用快速幂算法来计算a^((p-1)/2) mod p的值,如果结果不等于+-1,则p一定不是素。 缺点:Solovay-Stassen素性检验算法比费马素性检验算法更加复杂,但依然存在漏报的情况。 3.Miller-Rabin素性检验算法 Miller-Rabin素性检验算法是一种基于费马小定理的素性测试算法,它是目前最常用的素性检验算法之一。它的原理是:如果p是一个素,a是小于p的正整,则a^(d*2^r) mod p = 1或者p-1,其中d是一个奇,2^r是p-1的一个因子;如果p不是素,那么对于任意小于p的正整a,a^(d*2^r) mod p != 1或者p-1。因此,我们可以在随机选择的a值下,使用快速幂算法来计算a^(d*2^r) mod p的值,如果结果不等于1且不等于p-1,则p一定不是素。为了提高精度,Miller-Rabin算法通常会多次进行检验。 优点:Miller-Rabin素性检验算法的误判率很低,可以满足绝大部分应用需求。同时,Miller-Rabin算法的时间复杂度比Solovay-Stassen算法更低。 联系:这三种算法都是基于数论定理进行素性检验的,但是原理和具体实现方法有所不同。费马素性检验算法和Solovay-Stassen素性检验算法都有漏报的情况,而Miller-Rabin素性检验算法的误判率较低。因此在实际应用中,Miller-Rabin算法更加常用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值