数论--素数

一、素数的概念

素数又称质数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。
(素数是数论中很重要的部分,所以对于一些素数的操作,需要十分的熟练)

二、判断素数的方法

1.最基础的方法(初学者经常用的方法)

bool isprime(int n)
{
    for(int i=2; i<n; i++)
        if(n%i==0)
            return false;
    return true;
}
//直接从2遍历到 n,如果之间可以被 n整除,则 n为合数,反之为素数

2.试除法(相当于上个方法的优化)

bool isprime(int n)
{
    for(int i=2; i<=sqrt(n); i++)//写成 i*i<=n可以稍微快一点,
        if(n%i==0)               //因为每次循环都会计算sqrt一次,也可以用变量存起来
            return false;
    return true;
}
/*
如果是合数的话,那么他的因数一定会有分布在sqrt(n)的范围内的,
就是说一个大于sqrt(n)的数要是可以被 n整除了,它的商肯定是要小于sqrt(n)的,
这时我们就不需要遍历 n个数字,而直接遍历sqrt(n)个数字就可以了
*/

上述两种方法完全运用了素数的概念,好理解,但是效率不高。
优化后的第二种方法也是初学者经常用的,虽然比第一种方法时间复杂度小不少,但是也是有O(n1/2),对于一般的题目只能判断到1012,所以对一些再大的数就很不友好

3.六素数法(这是在翻博客的时候发现的一种方法,在百度百科上也有提到,先放到这里)
建议看一下>>百度百科的“六素数偶”部分<<便于理解以下代码

bool isprime(int n)
{
    if(n==1)
        return false;
    if(n==2||n==3)
        return true;//1,2,3的情况特殊判断
    if(n%6!=1&&n%6!=5)
        return false;
    int k=sqrt(n)+1;
    for(int i=5; i<k; i+=6)//这一部分和试除法类似
        if(n%i==0||n%(i+2)==0)
            return false;
    return true;
}

这个的时间复杂度应该是 O(n1/2/3),相比上两种方法,效率还是提高了一些。
三、区间上的素数数量

一般来说,一个和素数相关的问题都是求[2,n]内的素数。如果用上边的方法一个一个来判断,时间复杂度为O(n*n1/2),如果n比较大,可能计算机要跑几分钟。所以这种方法难免会超时,下面说一下可以解决一个区间上素数问题的算法。
1.埃式筛法(埃拉托斯特尼筛法)
基本思想:素数的倍数一定不是素数
数论--素数_第1张图片
(动图出处: https://www.cnblogs.com/findwg/p/4901219.html)

const int maxx=1e7;//定义空间大小,1e7大约10MB
int prime[maxx+1];//存放素数
bool visit[maxx+1];//true表示被筛掉,表示素数
int E_sieve(int n)//埃式筛,计算[2,n]内的素数
{
    for(int i=0; i<=n; i++)
        visit[i]=false;
    for(int i=2; i*i<=n; i++)//核心代码
        if(!visit[i])
            for(int j=i*i; j<=n; j+=i)
                visit[j]=true;//标记成非素数
    int k=0;//作为统计个数的变量
    for(int i=2; i<=n; i++)
        if(!visit[i])
            prime[k++]=i;//储存素数
    return k;
}

时间复杂度:可以看出来,在核心代码的那部分,一共要进行的循环次数为O(n/2+n/2+n/5+…),即O(nloglog2n),有时候也近似看成O(n)
可以看出,埃式筛法还不错,但是也做了一些无用功,某个数字会被筛选多次,比如12这个数字,在2和3的时候就筛选了两次。
既然有可优化的地方,那必然有更好的算法
2.欧拉筛法(欧拉线性筛)
原理:由于所有合数都有一个最小质因子,所以在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。

int Oula_gprime()
{
    int get=0;
    memset(v,0,sizeof(v));
    v[0]=v[1]=1;
    for(int i=2; i<=maxx; i++)
    {
        //cout<<"# i = "<
        if (!v[i])
            prime[get++]=i;
        for (int j=0; j<get&&i*prime[j]<=maxx; j++)
        {
             //cout<<"j = "<
             //cout<<"i * prime[j] = "<
            v[i*prime[j]]=1;
            if (i%prime[j]==0)
                break;        
         }
         //cout<
    }
    return get;
}

精华部分(也算是对埃式筛法的优化部分):

for (int j=0; j<get&&i*prime[j]<=maxx; j++)
{
    v[i*prime[j]]=1;
    if (i%prime[j]==0)
        break;
}
/*
我们知道任何合数都能表示成多个素数的积。
所以,任何的合数肯定有一个最小质因子。
我们通过这个最小质因子就可以判断什么时候不用继续筛下去了。
当 i是 prime[j]的整数倍时,i*prime[j+1]肯定再次被筛,就跳出循环。
就比如 i=6,prime[j]=2(这时候 prime已经有了2,3,5),
i%prime[j]==0,所以就可以跳出循环。
因为在下面 prime[j]=3的时候,i*prime[j]=18,
在下一次 i=18的时候,会再循环一次,prime[j]=5的时候也一样
*/

可以发现,在下图的 i:2~8项中,i * prime[j]的值一直没有重复
打表也可以发现,所有的 i * prime[j]一直都不会重复出现
这也就验证了我们一开始的对欧拉筛的另一种叫法:欧拉线性筛,
欧拉筛的时间复杂度也就的确可以做到O(n)的大小。

数论--素数_第2张图片

四、结语
无结语

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