【算法】篩選法統計素數--埃拉托色尼篩


埃拉托色尼的篩子

生成素數有很多方法,本文介紹的算法是一種高效的篩選算法 ---埃拉托色尼篩選法。

比如,要產生[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 (int 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。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM