求范围内的质数的个数

首先上概念:

质数又称素数。一个大于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);
	}
}

也可以输入两个端点值来求中间的所有的质数.这个大家可以尝试一下. 

 这个是之前刷题看到的,然后原文找不到了,我在里面加了一些自己的想法和分析,希望能够帮到大家.

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

封奚泽优

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值