我们在做题时往往要判断一个数是不是素数。
一般人能想到的就是根据定义,除了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、每个数都没有被重复地删掉