首先上概念:
质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数(规定1既不是质数也不是合数)。
要求输入一个整数n(n>1),求[2,n)范围内质数的个数.
目录
方法一(素数筛法):
思路解析:首先知道n是大于1的自然数,判断n(n>1)是否是质数,最开始学到的方法就是把[2,n-1]的数都当成除数,n为被除数,如果说n都不能够整数,就说明在[2,n-1]的范围之内他没有因子,那也说明n为质数.
#include<iostream>
using namespace std;
int main(){
int n,primeCount=0;
cout<<"请输入一个大于1的正整数n:";
cin>>n;
if(n<2){
//n小于2就不满足要求了,负数,0,和1都不是负数
primeCount=0;
}else{
for(int i=2;i<n;i++){
//枚举[2,i-1)的所有整数,判断是否会是i的因子
for(int j=2;j<i;j++){
if(i%j==0){
break;
}
}
//最后i==j的话就说明,j没有在循环中中途跳出
if(i==j){
primeCount++;
}
}
}
cout<<"当前范围内质数的个数有 "<<primeCount<<" 个"<<endl;
return 0;
}
这个内层循环写成j<n程序也没有错误,分析下来发现:
最开始是j是小于i的时候,就和j小于n是一样的,如果i是不是质数,在之前也就跳出了,然后就是临界值的时候,因为n是大于i的,因为外层的循环是i<n,当里面改成j<n的时候,刚好在i==j的时候i%j==0,他能够被自己整除,在跳出循环之后的判断也是满足要求的,故而和理想中的结果相同.
总而就是如果将j<i写成了j<n的,会在i==j的时候满足i%j==0(自己被自己整除)跳出循环,跳出循环的判断i==j恰好也是满足要求的,于是也会当成质数被统计.
这种方案就有很多的比较是没有必要的,假设n有很多的因子,如果他有一个大于根号n的因子,就一定会有一个小于根号n的因子,y=x1*x2,当x1=x2时,如果一个数大于x,要想最后的结果等于y,另一个数就一定是小于x的.放在程序中就是判断的时候有很大的一部分是没有必要判断的,只需要判断sqrt(n)的范围里他有没有因子就行了.改进之后:
#include<iostream>
#include<cmath>
using namespace std;
int main(){
int n,primeCount=0;
cout<<"请输入一个大于1的正整数n:";
cin>>n;
if(n<2){
//n小于2就不满足要求了,负数,0,和1都不是负数
primeCount=0;
}else{
for(int i=2;i<n;i++){
int tmp=sqrt(i);
bool flag=true;
//这个需要取到等于呢
for(int j=2;j<=tmp;j++){
if(i%j==0){
flag=false;
break;
}
}
//最后i==j的话就说明,j没有在循环中中途跳出
if(flag){
primeCount++;
}
}
}
cout<<"当前范围内质数的个数有 "<<primeCount<<" 个"<<endl;
return 0;
}
这个需要注意的边界值,不放心的话可以稍微的扩大一下tmp的范围,就比如17*17=289;我之前统计的时候有时候就会忽略这个,如果没有取到等于的话,[2,16]的整数都不能满足要求,289就会被当成质数,所以需要取到等于符号.有些写法就是求tmp的时候是用sqrt(i+1),类似也不是int,是double类型,因为double到int会有精度的损失.
对于判断的话是不好判断的,是比较j和sqrt(j)的关系还是什么的,后面我干脆就是直接用一个bool变量来记录他是否是质数,每一次在外层循环和内存循环中间都需要对tmp和flag变量进行初始化,flag为true就表示他是质数,当不满足要求是就会变成false,故而每次都需要初始化,不然有一次i不是质数的时候,就一直不是质数了.
还有一种写法就是:
#include<iostream>
using namespace std;
int main(){
int n,primeCount=0;
cout<<"请输入一个大于1的正整数n:";
cin>>n;
if(n<2){
//n小于2就不满足要求了,负数,0,和1都不是负数
primeCount=0;
}else{
for(int i=2;i<n;i++){
bool flag=true;
//这个需要取到等于呢
for(int j=2;j<=i/2;j++){
if(i%j==0){
flag=false;
break;
}
}
//最后i==j的话就说明,j没有在循环中中途跳出
if(flag){
primeCount++;
}
}
}
cout<<"当前范围内质数的个数有 "<<primeCount<<" 个"<<endl;
return 0;
}
这种不是用sqrt来求根号,是直接除2,来判断,同样端点值是需要取到,这个我理解就是,x1*x2=y;
因为质数的定义 除了1和它自身外,不能被其他自然数整除的数叫做质数,那x1取值最小就是2是吧
那另一个数x2最大不就是y/2,所以临界值就变成了i/2.
内存循环这样改写之后,外层循环同样可以化简,我们知道偶数都有一个2的因子所以考虑的时候是不需要考虑偶数的,判断的时候只要判断奇数即可了,所以这又可以省去一些无效的判断。
从2开始判断可以把2单独拿出来,然后从3开始,没有累加的步长就不是1了,变成了2,判断就是3,5,7....这个大家可以自己去试一下哦。
方法二(埃氏塞法):
因为是统计质数的个数,我们就可以把不是质数的标记,剩下的就都是质数了,这个那怎么实现呢?
我们知道一个数乘以2,乘以3...都不再算是质数了,所以我们就可以统计乘以这些数之后的数,比如6就是3*2之后的结果,8就是4*2之后的结果,类似的就可以记录[2,n-1]中所有的合数,也就知道了所有的质数了.
#include<iostream>
using namespace std;
int main(){
int n,primeCount=0;
cout<<"请输入一个大于1的正整数n:";
cin>>n;
if(n<2){
//n小于2就不满足要求了,负数,0,和1都不是负数
primeCount=0;
}else{
int *isPrime=(int*)malloc(sizeof(int)*n);
//首先用一个数组来记录该数是否是质数
memset(isPrime,0,sizeof(int)*n);
for(int i=2;i<n;i++){
//只需要为质数的时候运行
if(!isPrime[i]){
//为0说明该数就是质数
primeCount++;
//把i的倍数标记成1,也就是不是质数
for(int j=i*i;j<n;j+=i){
isPrime[j]=1;
}
}
}
}
cout<<"当前范围内质数的个数有 "<<primeCount<<" 个"<<endl;
return 0;
}
要注意的几个地方,为什么只需要质数才进行内层的循环,因为如果是合数他早就已经标记过了,他的倍数都也已经标记过了,以2为例,他标记的是4,6,8,10...那么6就已经标记过了,这就没有必要再写了,所以当他为质数的时候才会提供新的因子来构成新的合数.
内层循环标记倍数为什么不是从2开始,这个也好理解,2*3和3*2标记一次就行了,从i*i开始直接省去了一些无效的比较,最开始就是j=i*i,然后逐渐的j+=i,也就是i*(i+1)...因为需要在范围之内,故而是j<n的.
两种方法都有自己的优点吧.这个不仅是可以统计个数,也可以得到每一个质数,只需要在累加个数的时候进行输出就行了.
输出就是下面的这种.用java写的.
import java.util.Scanner;
//素数筛法
public class prime {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.print("请输入一个大于1的正整数:");
int n=sc.nextInt();
if(n<2) {
System.out.println("输入非法");
return;
}
//统计质数的个数
int primeCount=0;
//一行10输出10个
final int wrapNumber=10;
for(int i=2;i<n;i++) {
int tmp=(int)Math.sqrt(i);
boolean flag=true;
for(int j=2;j<=tmp;j++) {
if(i%j==0) {
flag=false;break; }
}
if(flag) {
primeCount++;
if(primeCount%wrapNumber==0) {
System.out.println(i);
}else {
System.out.print(i+" ");
}
}
}
System.out.printf("\n在[2,%d)内一共有 %d 个质数\n",n,primeCount);
}
}
也可以输入两个端点值来求中间的所有的质数.这个大家可以尝试一下.
这个是之前刷题看到的,然后原文找不到了,我在里面加了一些自己的想法和分析,希望能够帮到大家.