筛法--朴素筛法和埃式筛法和线性筛法

2023/6/1 1:22:12

本文主要是介绍筛法--朴素筛法和埃式筛法和线性筛法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

朴素筛法:

#include <iostream>
#include <algorithm>
using namespace std;
const int N=1000010;
int primes[N],cnt;
bool st[N];

void get_primes(int  n){
    
    for(int i=2;i<=n;i++){
         
        if(!st[i])//st==0 代表的是这个数是质数
        {
            primes[cnt++]=i;
        }
        for(int j=i+i;j<=n;j+=i)
        {
            st[j]=true;
        }
    }    
}
int main(){
    int n;
    cin>>n;
    get_primes(n);
    
    cout<<cnt<<endl; 
}

 这个朴素算法的思路就是,枚举这些数,首先在st数组初始化时,就是已经把这个数组内的值都初始化为0,也就是说都是看成是质数。。。。

然后,如果这个数确实是质数,那么我们就可以把这个数放入我们存质数的数组里面去,然后对质数的个数进行增加,并且,我们把这个质数的倍数(2倍,3倍,4倍。。。。。。)的st都标记为合数(st=1),范围是这个数小于n。。

但是我我们举个例子就可以发现这个朴素的算法有明显的可以优化的地方

让我们看这个代码段

   for(int i=2;i<=n;i++){


 for(int j=i+i;j<=n;j+=i)
        {
            st[j]=true;
        }


}

假如这个n是等于12

i=2    倍数有2,4,6,8,10,12  (都会被筛)

i=3     倍数有3,6,9,12  (都会被筛)

i=4     倍数有4,8,12   (都会被筛)

............

从这个中间我们可以看到4,6,8和12是多次被赋值为true的,这个方就会降低算法的效率。。。。这个地方我们就要想办法优化。。。。。

就有一种想法就是:我们不要把所有数的倍数都进行一个赋值,而是有一部分的数进行倍数的赋值------这个数就是质数

也就是说我们对质数的倍数进行赋值----也就是一个埃及人发明的。。---------我们称之为埃式筛法

 

代码如下:

也就把这个代码段放入循环中:

#include <iostream>
#include <algorithm>
using namespace std;

const int N=1000010;
int primes[N],cnt;
bool st[N];

void get_primes(int  n){
    
    for(int i=2;i<=n;i++){
         
        if(!st[i])//st==0 代表的是这个数是质数
        {
            primes[cnt++]=i;
//。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
                for(int j=i+i;j<=n;j+=i)
        {
            st[j]=true;

// 这个是被放进来的部分
            
        }
//。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
        }
    
    }    
}
int main(){
    int n;
    cin>>n;
    get_primes(n);
    
    cout<<cnt<<endl;
    
    
    
    
}

 在上面的埃式筛法中,我们做到了一个优化,但是这个优化也不是很完全:

还是举这个例子来说,n=12时

i=2    倍数有2,4,6,8,10,12   (都会被筛)

i=3     倍数有3,6,9,12   (都会被筛)

i=4     倍数有4,8,12       但是根据算法的思路,我们可以知道,4不是质数所以他的倍数不会被筛,就是这种地方优化了算法的效率。。

 

然后就是线性筛法,比埃式筛法更快原因是:做到了每一个数都只被筛一遍-----也就是每一个数只会被他的最小的质因子去筛掉(核心)

#include <iostream>
#include <algorithm>
using namespace std;

const int N=1000010;
int primes[N],cnt;
bool st[N];

void get_primes(int  n){
    
    for(int i=2;i<=n;i++){//外层循环是枚举所有的数的
  
     if(!st[i]) primes[cnt++]=i;
     for(int j=0;primes[j]<=n/i;j++)
     //j是从小到大枚举所有的质数 
     
     {
         st[primes[j]*i]=true;
         if(i%primes[j]==0) break;
     }
}
}
int main(){
    int n;
    cin>>n;
    get_primes(n);
    
    cout<<cnt<<endl;
        
}

........................................................................................................

引用:(这个地方是没看懂他怎么想的,但是这个例子部分还是很明显易懂的)

欧拉筛法任何一个合数都可以写成几个质数相乘的形式,也就是任何一个合数至少有一个最小质因子,而每一个质数的倍数一定不是质数,所以我们可以通过每一个合数的一个最小素因子去筛掉它,并且只筛掉一次,这样就把时间复杂度降到了O(n)。其中就是这条语句起着至关重要的作用:if(i%prime[j]==0)break;为什么呢?举个栗子:假设当前i=9,prime素数表里有2,3,5,7,即先筛掉2*9=18,然后筛掉3*9=27,判断9%3==0,退出当前循环。因为9=3*3,如果继续筛掉5*9,相当于筛3*3*5=3*15,而这个数在i=15时筛掉就可以了。因此,可以用以下的推导证明上面的结论:假设pi是当前i的最小质因子,那么素数表有p1<p2<...<pi<pi+1<...,枚举当前质数表p[j],j从小到大枚举,直到i%p[j]==0这个条件时,前面所有质数p[j]都是i*p[j]的最小质因子,如果继续枚举下去,即下一个要被筛掉的合数为i*prime[j+1],因为i中已经含有一个最小质因子prime[j],设i=k*prime[j],则i*prime[j+1]=k*prime[j]*prime[j+1]=k'*prime[j],显然k'>i,k'*prime[j]可以留在后面被筛掉。换句话说,prime[j+1]已不是i*prime[j+1]的最小质因子,i*prime[j+1]必然可以被一个更小的质数prime[j]筛掉,那么k'=i*prime[j+1]/prime[j]=k*prime[j+1]就一定比i大,即下一次i遍历到i=k'时,k'*prime[j]却被其最小素因子prime[j]筛掉,而不是prime[j+1],这就相当于再筛选了一次(增加了时间复杂度),所以只需将j遍历到i的最小素因子prime[j]就可以了,即保证每一个合数只被其最小质因子筛掉,这样把时间复杂度完美地从O(nloglogn)降到了O(n)。

................................................................................................................

根据自己的分析:

我们用每个合数的最小质因子来进行筛选之所以被称为线性,是因为: 1-n之内的任何一个合数一定会被筛掉,而且筛的时候只用最小质因子来筛,

每一个数都只有一个最小质因子,所以每个数都只会被筛一次,因此线性筛法是线性的。

具体的步骤就是:之前每筛到一个素数j,就将这个素数放入到primes[]中,在每次循环中,从头开始遍历primes[],筛选出的合数就是primes[j]*i,

为了保证每个数只被筛选一次,就要保证每个合数都是被其最小的质因子筛选掉的,所以我们就要找到primes【j】*i的最小质因子,所以我们需分情况讨论:

首先我们知道,primes【j】*i 的最小质因子应该是min(primes【j】,i的最小质因子),也就是说我们需要找到二者中的最小值。。。。。

1.当i%primes[j]不等于0时

此时primes[j]小于i的所有质因子,因为primes[j]是从小到大进行枚举的,如果primes[j]是i中质因子之一,那么就会满足i%primes[j]等于0。

所以我们就可以知道了i的质因子还在后面,还没有加入到我们的primes数组中,所以也就是说,primes【j】就是i*primes【j】的最小质因子

------------所以这种情况下,primes[j]是可以用来筛这个合数 primes【j】*i 的。。。。

2. 当i%primes[j]等于0时

说明此时,primes【j】就是i的最小质因子,原因就是:primes[j]是从小到大进行枚举的,这个时候,很明显我们就可以知道primes【j】就是i*primes【j】的最小质因子

综合以上的情况我们就可以知道用这个primes[j]是可以用来筛这个合数 primes【j】*i 是合理的,因为在这两种情况下,primes【j】就是i*primes【j】的最小质因子。。。。

如何保证这个数只被筛一次,也就是说我们里面这个内循环,每次只能筛一个数走,也就是说,只有一个地方会被设置为true每一次。

所以一旦这个primes里的质数筛走一个数之后就要内循环break,这个时候我们就需要找到这个break的条件。。。

循环应该在i % primes[ ] == 0时停止。。。。。

在一些测试的数据上,如果n<1e6,线性筛和埃式筛的时间效率差不多,如果n>1e7的时候,线性比埃式要快了一倍左右。。。。

1e6:

 1e7:

 

 

线性筛法是对朴素筛法的进一步优化,埃式筛法的缺陷在于,对于同一个合数,可能被筛选多次。为了确保每一个合数只被筛选一次,我们用每个合数的最小质因子来进行筛选
之所以被称为线性,是因为: 11 ~ n� 之内的任何一个合数一定会被筛掉,而且筛的时候只用最小质因子来筛,每一个数都只有一个最小质因子,所以每个数都只会被筛一次,因此线性筛法是线性的.
具体操作方法为:之前每筛到一个素数,便将这个素数放入到primes[ ]中,在每次循环中,从头开始遍历primes[ ], 筛选出的合数为 primes[j]×i������[�]×�为了保证每个数只被筛选一次,就要保证每个合数都是被其最小质因子筛选掉的,即要找出 primes[j]×i������[�]×� 的最小质因子,所以我们分两种情况讨论:
首先说明:primes[j]×i������[�]×� 的最小质因子应该是 min(���(primes[j]������[�] , i� 的最小质因子),即二者中的最小值
1.当 i� % primes[j]≠0������[�]≠0 时说明此时 primes[j]������[�] 小于 i� 的所有质因子,因为 primes[j]������[�] 是从小到大进行枚举的,如果 primes[j]������[�] 是 i� 的质因子之一,那么应该满足 i� % primes[j]=0������[�]=0 才对。所以此时 primes[j]������[�] 是 primes[j]×i������[�]×�的最小质因子
2.当 i� % primes[j]=0������[�]=0 时说明此时 primes[j]������[�] 是 i� 的最小质因子(因为 primes[j]������[�] 是从小到大进行枚举的),所以此时primes[j]������[�] 也是 primes[j]×i������[�]×�的最小质因子
综上,使用 primes[j]������[�] 来筛选 primes[j]×i������[�]×� 是可行的,因为两种情况下 primes[j]������[�] 都是 primes[j]×i������[�]×� 的最小质因子
那么如何保证每个数都只被筛选一次?即循环应该在何时结束?循环应该在i % primes[ ] == 0的时候中止,理由如下:
当 i� % primes[j]=0������[�]=0 的时候如果不中止,那么将进入下一次循环,下一次循环要筛掉的数字是 primes[j+1]×i������[�+1]×� 。对于 primes[j+1]×i������[�+1]×� ,i� 的值没有变,和上一步满足 i� % primes[j]=0������[�]=0 时的 i� 是一样的,所以当前 i� 的最小质因子仍为 primes[j]������[�];但是当前为 primes[j+1]������[�+1] ,即比上一次循环的 primes[j]������[�] 要大,那么此时 primes[j+1]������[�+1] 和 i� 的最小质因子 (primes[j])(������[�]) 相比,较小值为 i� 的最小质因子 (primes[j])(������[�]) ,所以 primes[j+1]×i������[�+1]×� 的最小质因子应该为 (primes[j])(������[�]) ,那么 primes[j+1]×i������[�+1]×� 这个数应该由 (primes[j])(������[�]) 来筛选掉,但是当前却是由 (primes[j+1])(������[�+1]) 筛选掉的,所以出现了重复筛选。
因此,为了保证所有数只被筛选一次,循环需要在 i % primes[ ] == 0的时候中止
若 n<106�<106 ,线性筛和埃氏筛的时间效率差不多; 若 n>107�>107 ,线性筛会比埃氏筛快了大概一倍
作者:GodLan链接:https://www.acwing.com/activity/content/code/content/5374176/来源:AcWing著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这篇关于筛法--朴素筛法和埃式筛法和线性筛法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程