代码钢琴家 阅读(38) 评论(0)

埃拉托色尼的筛子

生成素数有很多方法,本文介绍的晒算法是一种高效的筛选算法 ---埃拉托色尼筛选法。

比如,要产生[2,n] 范围内的所有素数,步骤如下:

 

1、构造一个2,3,4,5,...n 的候选数序列 A 。

2、不断的去除(筛掉)序列A中的非素数。

     ①去掉2的倍数 。

     ②再去掉3的倍数。

     ③去掉4的倍数(不需要,因为在第一步已经被去掉了)  去掉5的倍数。

     ④去掉6的倍数

     ⑤去掉7 的倍数

        ... ...   一直到不能再去除为止。

 

3、经过反复的筛选去除后,序列A就只剩下了[2,n]内的素数

 

 

例子:产生[2,25]范围的素数序列

 

 

 

代码实现

 

/*
使用一个bool 数组。数组的下标代表待筛选的数,而用数组元素的值代表 数的存在与否。

如 A[p] = true  表示数p 存在,没有被去除。则p为素数

*/

void select_prime(int n)
{
    if (n < 2) return ;              
    
    const bool exist     = true;
    const bool not_exist = false;


    bool*A = new bool[n + 1];    //会浪费 数组的第一 和第二个元素空间.因为 0 和 1 既不是素数,也不是合数。
    memset(A, exist, sizeof(bool)*(n + 1));  //初始化为exist

    for (int p = 2; p*p <=n; p++)
    {
        if (A[p] == exist)         //
        {                          //
            int j = p*p;          //
            while (j <= n)        //
            {                     //
                A[j] = not_exist; //
                j += p;           // 
            }                     //
        }                         //
    }

    //至此 ,使得A[p]为true 的p都是素数。这里不再具体操作,取决于你自己的实现。如打印,或者转存

    delete[] A;

}

 

 

代码解读

整体代码,估计就是for循环那段代码可能不好理解。我们先分析for内部的代码。

if (A[p] == exist)        //如果p是素数
 {                        
      int j = p*p;   //则从p*p开始 去除
      while (j <= n)        
      {                     
           A[j] = not_exist;    //标记为   去除状态
           j += p;              //递增到下一个倍数
      }                     
}      

  

 

也就是说,对于一个数p,会依次去除  p*p  ,  p(p+1) , p(p+2) .... p(p+k)   【p(p+k)<=n】

前面不是说要去除 p 的所有倍数的吗?那 2p ,3p  4p ...p(p-1)怎么不去除呢?

他们已经被去除了。因为当前我们要消去 p 的倍数,那么,之前一定去除了 p-1 , p-2 ,p-3 ... 4 , 3 , 2  的这些数 的倍数,So , p(p-1)  , p(p-2) .... p3  p2 显然在之前的操作中被去除。

也正是因为这个原因,最外面的for循环,p 的值从 2  到   ,而不必从 2 到 n。为了避免使用sqrt函数带来消耗,我使用了乘法,是同样的效果。

 

即便通过技巧减少不必要的去除操作,上面的代码依然存在重复去除的可能。比如数字35,在去除5的倍数时被标记为  去除 ,在去除7的倍数时,也会再次被标记 。当然这不影响结果的正确性。但是如果要统计素数的个数,就需要小小的修改一下代码了。

 

统计素数的个数

 

int countPrime(int n)
{
    if (n < 2) return 0;

    const bool exist = true;
    const bool not_exist = false;

    unsigned  count = n - 1;      //假设全部是素数
    

    bool*A = new bool[n + 1];   
    memset(A, exist, sizeof(bool)*(n + 1));

    for (size_t p = 2; p*p <= n; p++)
    {
        if (A[p] == exist)
        {
            int j = p*p;
            while (j <= n)
            {
                if (A[j] == exist)     //只有没被去除,才做去除操作。避免重复统计
                {
                    A[j] = not_exist;
                    count--;           //减少1个
                }
                j += p;
            }
        }
    }


    delete[] A;

    return count;

}

 

这个算法的速度比我的这篇文章中最好的solution还要快10倍,很是强悍。但有个缺点就是空间复杂度为O(n)。

 

使用这个算法解决leetcode题目后的runtime统计,可以发现这个算法超越了98.89%的Ac。