素数判断

 ----- update on 2020 03 03 ------------

0.总结

0.1 线性筛素数

#include<iostream>
#include<cstdio>
using namespace std;
const int maxN = 1005;
const int maxC = 1005;//存储maxN范围内的素数个数
 
int arr[maxN];//原始数组 
int prime[maxC];//存储素数 
 
int main(){
	int n,q;
	cin >> n>> q;
	fill(arr,arr+maxN,1);
	int count = 1;
	for(int i = 2;i <= n;i++){				
		if(arr[i]!=0){
			prime[count++] = i;
		} 				
		for(int j = 1; j<=count && i * prime[j] <=n; j++){
			arr[i*prime[j]] = 0;//不是素数 								
			if(i % prime[j] == 0){
				break; 
			}
		}
	}
	int num;
	while(q--){
		cin >> num;
		cout << prime[num]<<"\n";
	} 
} 

-------- -----  -----  -----  -----  -----  -----  -----  -----  ----- 

 

1.前言

由于最近在刷PAT题,我想想这些天接触到的一些新的知识,如下:

  • (1)如何判断一个数是否为素数?
  • (2)如何求出一个数字范围内的所有素数?并且使得这个过程尽量高效?

假设读者都具有以下的知识:

  • (1)一个数是素数的条件是:它只能被1和其本身整除。
  • (2)1不是素数!

 

2.设计算法

  • (1)暴力法

我在这里姑且称其为暴力法,其实这是最为笨拙的一中方法,代码如下:

 

#include <stdio.h>
 
void isPrime(int number){
	int j ,i = 0 ;
	for(j = 2;j <= number;j++){
		for(i = 2;i < j;i++){
			if(j % i == 0){
				
				break;
			}
		}
		if(i == j){
			printf("%d ",j);
		}	
	}	
}
int main(){
	isPrime(20);
}

我们对上面的代码稍作分析,就会发现,我们有很多工作是多于的,比如说,我们对一个数a来说,它除以比其1/2还要大的数时,其余数肯定不为0,所以我们可以将代码这么修改:

 

#include <stdio.h>
 
void isPrime(int number){
	int j ,i = 1 ;
	for(j = 2;j <= number;j++){		
		for(i = 2;i <= j /2 ;i++){
			if(j % i == 0){				
				break;
			}
		}
		if(i > j/2){
			printf("%d ",j);
		}	
	}	
}
int main(){
	isPrime(20);
}

需要注意的地方是:for()循环结束的条件,是i<=j/2,和判断是否输出的条件if(i > j/2),这里需要针对4这个数进行修改。虽然将判断的数据缩小了一半,但是这仍然是不够得,在一般的算法题中,这样还是会超时。

下面我们对其再进行优化一下。

  • (2)优化后的暴力枚举

现在假设有一个数A,它有除1和本身之外的两个因子,分别称其为m,n即有m*n=A,那么肯定有m<sqrt(A)&&n>sqrt(A)或者m>=sqrt(A)&&n<=sqrt(A)。这里的sqrt(A)代表的是A的平方根。因为一个数的两个因子肯定是在这个数的平方根的两边。所以我们只需要遍历平方根左边的所有数字,如果有一个数字是其因子,那么在其平方根右边,肯定存在一个数也是其因子。所以就有了下面的这个算法:

#include <stdio.h>
#include <math.h>

void isPrime(int number){
	int j ,i = 1 ;
	for(j = 2;j <= number;j++){		
		for(i = 2;i < sqrt(j) ;i++){
			if(j % i == 0){				
				break;
			}
		}
		if(i > sqrt(j)){
			printf("%d ",j);
		}	
	}	
}
int main(){
	isPrime(20);
}

但是当我们处理一个大于10^5的素数时,这样的算法还是不够快,于是有了下面的这样的神奇算法。

  • (3)筛法

筛法有很多种,这里讲最重要的,也是复杂度最低的线性筛法。线性筛法成立的基础是:唯一分解定理

【下面所要说的因子均是除了1和本身之外】【我们给定一个数字范围,比如求20之前的所有素数】我们观察数字2,在此之前没有因子,所以其是素数,输出。---->推导出所有公因子中有2的数则不是素数,于是

  • step1》去掉了4,6,8,10,12,14,16,18,20...这些数

接着循环到数字3,发现它并没有被去掉,所以其是素数,输出。接着按照上面的方法,在这个数列中去除因子为3的数,于是

  • step2》去掉了9,15...这些数。

接着往下找第一个没有被去除的数,为数字5,所以为素数,输出

……

如此循环直到最后一个数字被处理。得到算法如下:

#include <stdio.h>
#include <math.h>

void isPrime(int number){
	int j ,i = 1 ;
	int array[100];
	
	for (i = 0;i<=number;i++){
		array[i] = i; 
	}

	for(j = 2;j <= number;j++){	//求20之前的素数	
		if(array[j]!=0){
			printf("%d ",array[j]);
		}
		for(i = 2;i <= number ;i++){
			if( i % j == 0){				
				array[i] = 0;
			}
		}
	}	
}
int main(){
	isPrime(20);
}


运算结果:

 

 

上面的筛法思想是正确的,但是代码实现真的好么?如果不好,那么好的算法该怎么实现呢?在说怎么实现之前,先说出上面的代码的问题(很明显,这里的错误有很多,下面会逐一列举。)

问题1:就像底部的第一条评论说的那样,这个筛法不是说优于其他算法么?但为啥这里的“筛法”复杂度还是O(n^2)?原因只有一个:那就是博主太菜了(过于真实)!!之前虽然理解了筛法的过程,但其实现是不正确的。

问题2:应该用找出来的素数进行筛选,而不是再次循环用j过滤(否则就与朴素的暴力无异了)。也就是说,上述的这几行代码是存在问题的。

if( i % j == 0){				
    array[i] = 0;
}

 

那么正确的筛法该如何实现?

筛法的主要思想如下:

(1)用素数筛掉不确定的数【切记,这里是用素数筛!】

(2)接下来第一个未筛掉的数就是素数(如:序列2,3,4,5,6。且2是素数,则接下来第一个未删掉的数就是3,故3就是素数,4会被2筛掉...)

(3)如何保证每个数都只筛一遍?比如数字6可以被素数2筛掉,也可以被素数3筛掉,那么到底怎么筛的呢?

这里采取的方法是用最小的素数筛,即用2筛。

如果上述实现三点能够实现,我们则可以得到一个近乎O(n)复杂度的求区间素数的样例。代码如下【结合络谷P3383写出代码】。

 

#include<iostream>
#include<cstdio>
using namespace std;
const int maxN = 1005;
const int maxC = 1005;//存储maxN范围内的素数个数
 
int arr[maxN];//原始数组 
int prime[maxC];//存储素数 

int main(){
	int n,q;
	cin >> n>> q;
	fill(arr,arr+maxN,1);
	int count = 1;
	//线性筛素数 
	for(int i = 2;i <= n;i++){		
		//说明是素数 
		if(arr[i]!=0){
			prime[count++] = i;
		} 
		
		//1.通过prime[j](是素数)筛掉其它的数 
		//2.j的下标小于count;
		//3.i*arr[j]的范围小于指定范围n
		for(int j = 1; j<=count && i * prime[j] <=n; j++){
			arr[i*prime[j]] = 0;//不是素数 
												
			//如果i可以整除prime[j],那么直接跳出 			 
			if(i % prime[j] == 0){
				break; 
			}
		}
	}
	int num;
	while(q--){
		cin >> num;
		cout << prime[num]<<"\n";
	} 
} 

为了更好的理解这段代码,应该自己debug一下整个过程。下面我给出我的一些理解点。

  • i用于大于prime[j]。为啥?因为prime[j]是i范围内的素数。

需要微观上把握代码细节,即该行代码有什么意义?为什么这么写?而不是东拼西凑的去尝试得出正确解。

arr[i*prime[j]] = 0;//不是素数 							

上面这句代码的作用是:prime[j]是i*prime[j]的最小公因子。

 

if(i % prime[j] == 0){
 break;
}

上面这三行代码是筛法的精髓。

  • 如果prime[j]能够整除i,(prime[j]是素数,i是合数)。可以知道后面的序列中能够被i整除的肯定可以被prime[j]整除。所以对于后面的序列中是i倍数的那些数,我们可以且应该用prime[j]删除(因为prime[j]是素数且小于i,这样就能保证只删一次)。

筛法求素数的模板,对每名algorithmer都是基础,应该牢记。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

说文科技

看书人不妨赏个酒钱?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值