zoukankan      html  css  js  c++  java
  • 【转载】一般筛法求素数+快速线性筛法求素数

    素数总是一个比较常涉及到的内容,掌握求素数的方法是一项基本功。

    基本原则就是题目如果只需要判断少量数字是否为素数,直接枚举因子2 。。N^(0.5) ,看看能否整除N。

    如果需要判断的次数较多,则先用下面介绍的办法预处理。


     一般的线性筛法

    首先先介绍一般的线性筛法求素数

    void make_prime()  {      
    	memset(prime, 1, sizeof(prime));
    	prime[0]=false;     
    	prime[1]=false;     
    	int N=31700;      
    	for (int i=2;  i<N;  i++)         
    	  if (prime[i]) {          
    		primes[++cnt ]=i;     
    		for (int k=i*i; k<N; k+=i)        
    			prime[k]=false;       
    	  }      
    	return;
    }   
    


    这种方法比较好理解,初始时,假设全部都是素数,当找到一个素数时,显然这个素数乘上另外一个数之后都是合数(注意上面的 i*i ,  比 i*2 要快点 ),把这些合数都筛掉,即算法名字的由来。

    但仔细分析能发现,这种方法会造成重复筛除合数,影响效率。比如10,在i=2的时候,k=2*15筛了一次;在i=5,k=5*6 的时候又筛了一次。所以,也就有了快速线性筛法。

     

    快速线性筛法

    快速线性筛法没有冗余,不会重复筛除一个数,所以“几乎”是线性的,虽然从代码上分析,时间复杂度并不是O(n)。先上代码

    #include<iostream>
    using namespace std;    
    const long N = 200000;   
    long prime[N] = {0},num_prime = 0;    
    int isNotPrime[N] = {1, 1};   
    int main()    
    {     
         	for(long i = 2 ; i < N ; i ++)       
           	{            
    		if(! isNotPrime[i])               
    	 		prime[num_prime ++]=i;  
    		//关键处1        
    		for(long j = 0 ; j < num_prime && i * prime[j] <  N ; j ++)
        		{               
    		      	isNotPrime[i * prime[j]] = 1;  
    	  		if( !(i % prime[j] ) )  //关键处2                  
    				break;           
    		}        
    	}        
    	return 0;   
    }  



    首先,先明确一个条件,任何合数都能表示成一系列素数的积。

    不管 i 是否是素数,都会执行到“关键处1”,


    ①如果 i 都是是素数的话,那简单,一个大的素数 i 乘以不大于 i 的素数,这样筛除的数跟之前的是不会重复的。筛出的数都是 N=p1*p2的形式, p1,p2之间不相等

    ②如果 i 是合数,此时 i 可以表示成递增素数相乘 i=p1*p2*...*pn, pi都是素数(2<=i<=n),  pi<=pj  ( i<=j )

    p1是最小的系数。

    根据“关键处2”的定义,当p1==prime[j] 的时候,筛除就终止了,也就是说,只能筛出不大于p1的质*i

    我们可以直观地举个例子。i=2*3*5

    此时能筛除 2*i ,不能筛除 3*i

    如果能筛除3*i 的话,当 i' 等于 i'=3*3*5 时,筛除2*i' 就和前面重复了。

    需要证明的东西:

    1. 一个数会不会被重复筛除。
    2. 合数肯定会被干掉。

    根据上面红字的条件,现在分析一个数会不会被重复筛除。

    设这个数为 x=p1*p2*...*pn, pi都是素数(1<=i<=n)  ,  pi<=pj ( i<=j )

    当 i = 2 时,就是上面①的情况,

    当 i >2 时, 就是上面②的情况, 对于 i ,第一个能满足筛除 x 的数  y 必然为 y=p2*p3...*pn(p2可以与p1相等或不等),而且满足条件的 y 有且只有一个。所以不会重复删除。


    证明合数肯定会被干掉? 用归纳法吧。


     类比一个模型,比如说我们要找出 n 中2个不同的数的所有组合 { i , j } ,1<=i<=n, 1<=j<=n,

    我们会这么写

    for (i=1; i<n; ++i )

      for (j=i+1; j<=n; ++j)

       {

        /////

       }

    我们取 j=i+1 便能保证组合不会重复。快速筛法大概也是这个道理,不过这里比较难理解,没那么直观。

    1楼提供的方法,我整理下

    //偶数显然不行,所以先去掉偶数。可以看作上面第一种的优化吧。

    //不过这种方法不太直观,不太好理解。

    我推荐这个算法! 易于理解。 只算奇数部分,时空效率都还不错!
    half=SIZE/2; 
    int sn = (int) sqrt(SIZE); 
    for (i = 0; i < half; i++) 
       p[i] = true;// 初始化全部奇数为素数。p[0]对应3,即p[i]对应2*i+3 
    for (i = 0; i < sn; i++) {    
    if(p[i])//如果 i+i+3 是素数
    {     
        for(k=i+i+3, j=k*i+k+i; j < half; j+=k) 
        // 筛法起点是 p[i]所对应素数的平方 k^2                                        
        // k^2在 p 中的位置是 k*i+k+i
        //    下标 i         k*i+k+i
        //对应数值 k=i+i+3   k^2         
           p[j]=false; 
    } 
    } 
    //素数都存放在 p 数组中,p[i]=true代表 i+i+2 是素数。
    //举例,3是素数,按3*3,3*5,3*7...的次序筛选,因为只保存奇数,所以不用删3*4,3*6....
    

    扩展阅读

    1. 打印质数的各种算法 http://coolshell.cn/articles/3738.html  里面有个用C++模板实现的,纯属开阔眼界,不怎么实用。
    2. 检查素数的正则表达式  http://coolshell.cn/articles/2704.html  数字n用  1111。。1 (n个1)表示,纯属坑爹

    转载自ACM 【程式=演算法+資料結構】@蜡笔小轩V的博客

  • 相关阅读:
    springmvc log4j 配置
    intellij idea maven springmvc 环境搭建
    spring,property not found on type
    intellij idea maven 工程生成可执行的jar
    device eth0 does not seem to be present, delaying initialization
    macos ssh host配置及免密登陆
    centos7 搭建 docker 环境
    通过rest接口获取自增id (twitter snowflake算法)
    微信小程序开发体验
    gitbook 制作 beego 参考手册
  • 原文地址:https://www.cnblogs.com/oycy0306/p/7515663.html
Copyright © 2011-2022 走看看