素数之为什么要用素数筛法

我们在做题时往往要判断一个数是不是素数。
一般人能想到的就是根据定义,除了1和它本身没有其他的因数。就是下边这种写法:
又可以称之为 傻瓜写法— n

import java.util.Scanner;

public class Test{ 
    public static void main(String[]args) {
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            int n= sc.nextInt();
            int i;
            for(i=2;i<n;i++)
                if(n%i==0)break;
            if(i==n) System.out.println("Yes");
            else System.out.println("No");
        }
    }
}

这种写法很容易想得到,但是有特别明显的缺点,当n太大时,虽然是限行时间复杂度,但我们调用这个方法判断一个数是不是素数时,往往是在别的循环里面,那样运行时间可就太久了。

那么能不能优化一下?—sqrt(n)
能,得跪着。

普通解法:

import java.util.Scanner;

public class Test{
    public static void main(String[]args) {
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            int n= sc.nextInt();
            int i;
            for(i=2;i<=Math.sqrt(n);i++)
                if(n%i==0)break;
            if(i>Math.sqrt(n)) System.out.println("Yes");
            else System.out.println("No");
        }
    }
}

虽然效率改进了不少,但是还是不够完美。
我们不妨换一种思路。

普通筛选法 — 埃拉托斯特尼筛法

如果访问到p,p没有被2~p-1中的数给筛掉,那么它必然是个素数

先说一下基本原理吧:

基本思想:素数的倍数一定不是素数

实现方法:用一个长度为N+1的数组保存信息(0表示素数,1表示非素数),先假设所有的数都是素数(初始化为0),从第一个素数2开始,把2的倍数都标记为非素数(置为1),一直到大于N;然后进行下一趟,找到2后面的下一个素数3,进行同样的处理,直到最后,数组中依然为0的数即为素数。

举个例子,N=20时,我们将得到这样一张表在这里插入图片描述
最后数组里面还是0的就是素数了。

代码实现如下: 我们用 is_prime[ ]来存储得到的素数 is_prime[ ]={2,3,5,7,11…}
tot 来表示得到的素数的个数

check[ ]用以判断一个数是不是素数。true表示是合数,false表示是素数

import java.util.Arrays;
import java.util.Scanner;

public class ASSF{
    public static void main(String[]args) {
        Scanner sc=new Scanner(System.in);
        int tot=0;
        int n=100000; //n表示最大元素
        boolean check[]=new boolean[n+1];
        int[] is_prime=new int[n+1];
        for(int i=2;i<=n;i++){
            if(!check[i]){
                is_prime[tot++]=i;
                for(int j=i+i;j<=n;j+=i)//合数能表示成比他小的质数的积
                	check[j]=true;
            }
            
        }    
//            check 为真表示不是素数
//            check 为假则是
        
        System.out.println(Arrays.toString(is_prime));
    }
}

输出

该算法时间复杂度为: O(n*loglogn),空间复杂度是O(n)
当 n = 1e6 时 , 复杂度约为 7.7 * 10^5 ,优于O( n )

不足之处也比较明显,手动模拟一遍就会发现,很多数被处理了不止1遍,比如6,在素数为2的时候处理1次,为3时候又标记一次,因此又造成了比较大的不必要处理…那有没有改进的办法呢…就是下面改进之后的筛法…

线性筛法 — 欧拉筛法

欧拉筛法的基本思想:在埃式筛法的基础上,让每个合数只被它的最小值因子筛选一次,以达到不重复的目的。

import java.util.Arrays;
import java.util.Scanner;

public class Test{
    static int MAXN=100005;
    static int MAXL=1299710;
    static int tot=0;
    public static void main(String[]args) {
        int prime[]=new int[MAXN];
        boolean check[]=new boolean[MAXL];
        for(int i=2;i<MAXL;i++){
            if(!check[i]){
                prime[tot++]=i;
            }
            for(int j=0;j<tot;++j){//***
                if(i*prime[j]>=MAXL)
                {
                    break;
                }
                check[i*prime[j]]=true;
                if(i%prime[j]==0)//***
                {
                    break;
                }
            }
        }
        System.out.println(Arrays.toString(prime));
    }
}

精华就在于 星号**标注 那两处,它们保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次,所以时间复杂度是O(n)

继续上面的例子进行一遍模拟 N=20
在这里插入图片描述

此过程中保证了两点:

1、合数一定被干掉了…

2、每个数都没有被重复地删掉

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值