原來 歐拉篩和埃式篩 這麼簡單——歐拉篩素數法和埃拉託斯特尼篩法

歐拉篩素數法和埃拉託斯特尼篩法篩法(歐拉篩和埃式篩)

由於對算法的學習比較吃力,所以爭取就是一點一點的理解算法,一句一註釋,將每一個存在的疑問都寫下來,致力於:方便像我這樣的小白快速學算法!

  • 首先,學習算法之前,先了解一些基礎的知識:
  • 素數(質數):如果一個大於1的自然數,如果只能被 1 和它本身整除,那麼這個數就是素數。
  • 合數:自然數中除了能被1和本身整除外,還能被其他數(0除外)整除的數。
  • 自然數1,既不是質數,又不是合數
  • 算數定理:任何一個合數(非質數),都可以以唯一的形式被寫成有限個質數的乘積,即分解質因數
  • 埃式篩

    • 算法思想如果一個數是素數,那麼它的倍數一定不是素數。我們要找n以內的所有素數,那麼把n以內的合數全部篩掉,剩下的就是素數了。
    • 算法代碼如下
    /**
     *找n以內的所有素數的個數
     *
     */
    int countPrimes(int n){
        vector<bool> isPrim(n,true);                      //定義n大小的數組,賦默認值true
        
            for(int i = 2;i < sqrt(n);++i){               //遍歷所有的數,遍歷到開方即可
                if(isPrim[i])                             //避免冗餘
                    for(int j = i*i; j < n; j+=i){    	  	                  
                        isPrim[j] = false;                //排除掉非質數,即把小於n的所有的i的倍數篩掉
                    }
            }
    
            int counter= 0 ;
            for(int i = 2; i < n;++i){
                if(isPrim[i]){
                    ++counter;                            //計數,true爲素數,false爲合數
                }
            }
            return counter;
    }
    
    • 算法釋疑

      • 爲什麼篩選到sqrt(n)

      引用力扣的一個例子,如下:

      後兩個乘積就是前面兩個反過來,反轉臨界點就在 sqrt(n)

      所以兩層的for循環,只需要其中一層可以遍歷sqrt(n)——n之間的數即可。

      12 = 2 × 6
      12 = 3 × 4
      12 = sqrt(12) × sqrt(12)
      12 = 4 × 3
      12 = 6 × 2
      
      • 爲什麼j = i*i開始

      試想以下,如果 j = i * ( i - 1) ,那麼, ( i - 1) < i , ( i - 1) 一定已經被篩選過了

      • 爲什麼j+=i

      正如前面所說,篩選掉素數的整數倍數,即篩選掉小於 n 的 i ,i+1 ,i+2 …倍的自然數

    • 時間複雜度O(n * log (log n) )

    • 缺陷

      正如上面的式子
      例如:對素數2進行篩選時,已經將自然數 6 篩選掉了; 但是到了對素數3處理時, 再一次篩選掉自然數6,對於這種情況,要想解決,所以就有了歐拉篩。

  • 歐拉篩

    • 算法思想:將合數分解爲一個最小質數乘以另一個數的形式,即 合數 = 最小質數 * 自然數,然後通過最小質數來判斷當前數是否被標記過。
    • 算法代碼如下
    /**
     *找n以內的所有素數的個數
     *
     */
    int countPrimes(int n){
        //歐拉篩選法
            vector<int>isPrim(n,0);                                 //記錄得到的素數,默認值爲0
            vector<bool>status(n,true);                             //記錄該值的狀態,默認值爲true
    
            int cnt = 2;                                    
            for(int i = 2 ; i < n; ++i){                            //遍歷所有的數
                if(status[i])  isPrim[cnt++] = i;                   //存放所有的素數
                for(int j = 2; (j < cnt)&&(i * isPrim[j] < n);++j){ //cnt表示素數數組中的個數  
                    status[i * isPrim[j]] = false;                  //篩去合數
                    if(i % isPrim[j] == 0)   break;                 //表示已經篩選過了
                }
            }
    
            int counter= 0 ;
            for(int i = 2; i < n;++i){
                if(isPrim[i]){
                    ++counter;                                       //計數,true爲素數,false爲合數
                }
            }
            return counter;
    }
    
    • 算法釋疑

      • 爲什麼isPrim[cnt++] = i

      運行第一遍時,先將最小質數2,存入數組進行篩選

      以後再次執行該語句時,符合判斷條件的值,將存入素數數組中

      • 爲什麼if(i % isPrim[j] == 0) break;

      可以這樣理解:

      在素數數組中的值,已經全部篩選過了,並且是遞增的

      如果取餘之後爲0,則表明找到了該值的最小質因數

      當i=21,prime[j]=3時,63被篩掉

      而 i=9,prime[j]=7時則不會,因爲i=9時,這個循環中,循環到prime[j]=3時,就會break出來,根本不會再繼續,故這種情況根本不會出現

      總結:一個合數被分解爲最小質因數*自然數,以最小質因數進行篩選

  • 時間複雜度O(n)

  • 參考文章

[1]: I
[2]: II
[3]: III

。◕‿◕。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章