原来 欧拉筛和埃式筛 这么简单——欧拉筛素数法和埃拉托斯特尼筛法

欧拉筛素数法和埃拉托斯特尼筛法筛法(欧拉筛和埃式筛)

由于对算法的学习比较吃力,所以争取就是一点一点的理解算法,一句一注释,将每一个存在的疑问都写下来,致力于:方便像我这样的小白快速学算法!

  • 首先,学习算法之前,先了解一些基础的知识:
  • 素数(质数):如果一个大于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

。◕‿◕。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页