求质数算法C++

点击打开参考 点击打开参考

问题描述:
①请实现一个函数,对于给定的整型参数 N,该函数能够把自然数中,小于 N 的质数,从小到大打印出来。
比如,当 N = 10,则打印出
2 3 5 7
②请实现一个函数,对于给定的整型参数 N,该函数能够从小到大,依次打印出自然数中最小的 N 个质数。
比如,当 N = 10,则打印出
2 3 5 7 11 13 17 19 23 29
方案1:试除法
基本思路是不断尝试是否能整除,是根据定义最容易想到的思路,但也是最平庸的解法。细分来说,有如下若干种思路:
1.首先我们了解一下素数的定义,所谓的素数指如果有一个正整数p只有两个因子1和p,则p为素数。而这里的试除即根据素数的定义,比如要判断自然数x是否质数,就不断尝试小于x且大于1的自然数,只要有一个能整除,则x是合数;否则,x是质数。这种做法,时间复杂度O(n2),其效率应该是最差的。
2.当我们判断一个数是否为素数时,试除时只需要从2到n的根号即可判断是否为素数。而且除2以外,所有的质数都是奇数则也就肯定不会被2整除,这样试除的遍历又可以不用遍历偶数。
3.一些更加聪明的程序员,会发现一个问题:尝试从3到√x的所有奇数,还是有些浪费。比如要判断101是否质数,101的根号取整后是10,那么,按照上述算法,需要尝试的奇数分别是:3,5,7,9。但是你发现没有,对9的尝试是多余的。不能被3整除,必然不能被9整除......顺着这个思路走下去,这些程序员就会发现:其实,只要尝试小于√x的质数即可。而这些质数,恰好前面已经算出来了(是不是觉得很妙?)。在具体的算法实现时,我们借助了一个单链表来存储上一次所有遍历出来的素数,顺便说一下,这就是算法理论中经常提到的:以空间换时间。
方案2:筛选法
估计很多人把筛法仅仅看成是一种具体的方法。其实,筛法还是一种很普适的思想。在处理很多复杂问题的时候,都可以看到筛法的影子。那么,筛法如何求质数捏,说起来很简单:
  首先,2是公认最小的质数,所以,先把所有2的倍数去掉;然后剩下的那些大于2的数里面,最小的是3,所以3也是质数;然后把所有3的倍数都去掉,剩下的那些大于3的数里面,最小的是5,所以5也是质数......
  上述过程不断重复,就可以把某个范围内的合数全都除去(就像被筛子筛掉一样),剩下的就是质数了。参见维基百科
如何确定质数的分布范围?
对于需求1,这个自然不是问题。因为在需求1中,质数的分布范围就是 N,已经给出了,很好办。代码如下:
s->data=i;s->next=L;L=s;
}
judge=0;}}
方案2:筛选法
分析思路:估计很多人把筛法仅仅看成是一种具体的方法。其实,筛法还是一种很普适的思想。在处理很多复杂问题的时候,都可以看到筛法的影子。那么,筛法如何求质数的,说起来很简单:
首先,2是公认最小的质数,所以,先把所有2的倍数去掉;然后剩下的那些大于2的数里面,最小的是3,所以3也是质数;然后把所有3的倍数都去掉,剩下的那些大于3的数里面,最小的是5,所以5也是质数......上述过程不断重复,就可以把某个范围内的合数全都除去(就像被筛子筛掉一样),剩下的就是质数了。
聪明的程序猿会构造一个定长的布尔型容器(通常用数组)。比方说,质数的分布范围是1,000,000,那么就构造一个包含1,000,000个布尔值的数组。然后把所有元素都初始化为true。在筛的过程中,一旦发现某个自然数是合数,就以该自然数为下标,把对应的布尔值改为false。全部筛完之后,遍历数组,找到那些值为true的元素,把他们的下标打印出来即可。
但是对于需求2,就难办了。因为需求2给出的 N,表示需要打印的质数的个数,那么这 N 个质数会分布在多大的范围捏?这可是个头疼的问题啊。
  但是,来应聘的程序猿如果足够牛的话,当然不会被这个问题难倒。因为素数的分布,是有规律可循滴——这就是大名鼎鼎的素数定理。
  稍微懂点数学的,应该知道素数的分布是越往后越稀疏。或者说,素数的密度是越来越低。而素数定理,说白了就是数学家找到了一些公式,用来估计某个范围内的素数,大概有几个。在这些公式中,最简洁的就是x/ln(x),公式中的 ln 表示自然对数(估计很多同学已经忘了啥叫自然对数)。假设要估计1,000,000以内有多少质数,用该公式算出是72,382个,而实际有78,498个,误差约8个百分点。该公式的特点是:估算的范围越大,偏差率越小。
  有了素数定理,就可以根据要打印的质数个数,反推出这些质数分布在多大的范围内。因为这个质数分布公式有一定的误差(通常小于15%)。为了保险起见,把反推出的素数分布范围再稍微扩大15%,应该就足够了。
  可能有同学会质疑俺:谁有这么好的记性,能够在笔试过程中背出这些质数分布公式捏?
  俺觉得:背不出来是正常滴。但是,对于有一定数学功底的应聘者,假如他/她知道质数分布公式,即便不能完整写出来,只要在答题中体现出:"此处通过质数分布公式推算范围",那么俺也是认可滴。
如何设计存储容器?
知道了分布范围,接下来就得构造一个容器,来存储该范围内的所有自然数;然后在筛的过程中,把合数筛掉。那么,这个容器该如何设计?
境界1
  照例先说说最土的搞法——直接构造一个整型的容器。在筛的过程中把发现的合数删除掉,最后容器中就只剩下质数了。
  为啥说这种搞法最土捏?
  首先,整型的容器,浪费内存空间。比方说,你用的是32位的C/C++或者是Java,那么每个 int 都至少用掉4个字节的内存。当 N 很大时,内存开销就成问题了。
  其次,当 N 很大时,频繁地对一个大的容器进行删除操作可能会导致频繁的内存分配和释放(具体取决于容器的实现方式);而频繁的内存分配/释放,会导致明显的CPU占用并可能造成内存碎片。
境界2
  为了避免境界1导致的弊端,更聪明的程序猿会构造一个定长的布尔型容器(通常用数组)。比方说,质数的分布范围是1,000,000,那么就构造一个包含1,000,000个布尔值的数组。然后把所有元素都初始化为 true。在筛的过程中,一旦发现某个自然数是合数,就以该自然数为下标,把对应的布尔值改为 false。
  全部筛完之后,遍历数组,找到那些值为 true 的元素,把他们的下标打印出来即可。
  此种境界的好处在于:其一,由于容器是定长的,运算过程中避免了频繁的内存分配/释放;其二,在某些语言中,布尔型占用的空间比整型要小。比如C++的 bool 仅用1字节
注:C++标准(ISO/IEC 14882)没有硬性规定 sizeof(bool)==1,但大多数编译器都实现为一字节。
境界3
  虽然境界2解决了境界1的弊端,但还是有很大的优化空间。有些程序猿会想出按位(bit)存储的思路。这其实是在境界2的基础上,优化了空间性能。俺觉得:C/C++出身的或者是玩过汇编语言的,比较容易往这方面想。
  以C++为例。一个bool占用1字节内存。而1个字节有8个比特,每个比特可以表示0或1。所以,当你使用按位存储的方式,一个字节可以拿来当8个布尔型使用。所以,达到此境界的程序猿,会构造一个定长的byte数组,数组的每个byte存储8个布尔值。空间性能相比境界2,提高8倍(对于C++而言)。如果某种语言使用4字节表示布尔型,那么境界3比境界2,空间利用率提高32倍。

#include <iostream>
#includ <cmath>
using namespace std;

//求小于m的所有素数
void prime_number(int m){
	int i,j,k;
	bool* Num;
	Num = new bool[m];
	for(int i=0;i<m;i++){
		if(i==0)
			Num[i] = false;
		else
			Num[i] = true;
	}
	k=1;
	while(k<m){
		for(i=k; i<m; i++){
			if(Num[i]){
				for(j=i+1; j<m; j++){
					if((j+1)%(i+1) == 0)
						Num[j] = false;
				}
				break;
			}
		}
		k = i+1;
	}
	for(i=0; i<m; i++){
		if(Num[i])
			cout<<i+1<<endl;
	}
	delete []Num;
}

//从小到大求n个素数
//素数定理可以给出第n个素数p(n)的渐近估计:p(n)约等于nlogn.实际上p(n)一般不会大于(1+15%)*nlogn.所以方案二根据这个思路先确定一个p(n)(肯定包含n个素数),再用筛选法排除p(n)的所有合数,最后输出n个素数即可
void prime_number(int m, int n){
	int i,j,k,count = 0;
	bool* Num;
	Num = new bool[m];
	for(i=0;i<m;i++){
		if(i==0)
			Num[i] = false;
		else
			Num[i] = true;
	}
	k = 1;
	while(k<m){
		for(i=k; i<m; i++){
			if(Num[i]){
				for(j=i+1;j<m;j++){
					if((j+1)%(i+1) == 0)
						Num[j] = false;
				}
				break;
			}
		}
		k = i+1;
	}
	for(i = 0;i<m;i++){
		if(Num[i]){
			cout<<i+1<<endl;
			count++;
		}
		if(count == n)
			break;
	}
	delete []Num;
}

int main(){
	int m;
	cout<<"please input a number larger than 2 "<<endl;
	cin >> m;
	prime_number(m);
	int n = m*log(m)*1.5;
	prime_number(n,m);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值