用算法求N(N>=3)之内素数的个数

首先,我们谈一下素数的定义,什么是素数?除了1和它本身外,不能被其他自然数整除(除0以外)的数

称之为素数(质数);否则称为合数。

根据素数的定义,在解决这个问题上,一开始我想到的方法是从3到N之间每个奇数进行遍历,然后再按照素数的定义去逐个除以3到

根号N之间的奇数,就可以计算素数的个数了。

于是便编写了下面的代码:

(代码是用C++编写的)

#include<iostream>
#include <time.h>
using namespace std;

#define N 1000000

int compuPrimeN(int);

int main(char argc, char* argv[])
{
	int iTimeS = clock();
	int iNum = compuPrimeN(N);
	int iTimeE = clock();

	cout << iNum << endl;
	cout << "算法时间:" <<iTimeE - iTimeS<<"毫秒"<< endl;
	getchar();
	return 0;
}

int compuPrimeN(int maxNum)
{
	//算法1
	int iNum = 1;  //起始记上2
	bool bPrime = true;
	for (int i = 3; i <= maxNum; i += 2)
	{
		bPrime = true;
	for (int j = 3; j <= (int)sqrt(i); j += 2)
	{
		if (i%j == 0)
		{
			bPrime = false;
			break;
		}
	}
	if (bPrime)
		iNum++;
	}

	return iNum;
}

运行后如图所示:

由此可见,算法的性能不是很好,在时间上还有很大可以优化的空间。

那么,该如何优化?

首先,我是想,既然去掉了2的倍数,那么能不能去掉3的倍数,但后来

发现,在第二个循环里第一个取余的就是3,那么3的倍数其实只计算了一次

就过滤,所有没有必要再往下思考。

后来我想到,在第二个循环里,3取余过了,如果没跳出循环,那么6,9之类的

应该不用继续取余,同理,5取余过了,那么10,15...就不该继续取余,因为取余

5不为0,那么取余10,15肯定也不为0.换言之,那么不该取余的其实是合数!!

why?因为如果是合数,那么比他根号本身小的数里肯定有它能取余的,也就是

之前我们想过滤掉不想取余的数,这样一来,其实我们只要在第二循环里取余

比其根号本身要小的质数就能判断出来了!而那些质数我们在求该数之前就已经

找出来了,那么我们只要将其记录下来就行了!!

于是乎,遵循乎该思路,我将compuPrimeN()函数重写,写出了第2个算法:

int compuPrimeN(int maxNum)
{
	//算法2
	int iNum = 1;  //记录素数总个数
	int iRecN = 1; //记录在数组内素数的个数
	bool bPrimeN = true;
	int sqrtMaxN = (int)sqrt(maxNum);
	//我们要记录小于sqrtMaxN内的素数,为使空间分配最优,大小为x/ln(x)*1.2,
	//因为科学家发现一个求素数大致范围的近似公式x/ln(x),
	//为了不数组越界,多加20%范围
	//注意maxNum为3时为特例,因为此处ln(根号3)为0
	int* iPrime = new int[maxNum == 3 ? 1 : (int)((float)sqrtMaxN / log(sqrtMaxN)*1.2)];

	for (int i = 3; i <= maxNum; i += 2)
	{
		bPrimeN = true;
		//只要取余范围内的素数就好了
		for (int j = 1; j < iRecN; j++)
		{
			if (i%iPrime[j] == 0)
			{
				bPrimeN = false;
				break;
			}
		}
		if (bPrimeN)
		{
			if (i <= sqrtMaxN)
			{
				iPrime[iRecN] = i;
				iRecN++;
				iNum = iRecN;
			}
			else
				iNum++;
		}
	}
	delete iPrime;
	return iNum;
}

运行后如图所示:

看,优化后算法的时间性能比原来好了19倍左右,

那能不能更快呢?

我想理论上是可以的,因为前面的算法都用到了一种思想,

事先过滤掉了2,3的倍数,如果我们能把5,7,11的倍数都

事先过滤掉那不是更快吗?

这里为什么没有9,因为9的倍数即是3的倍数啊,咦?好像

发现了什么,和算法2的思想有点类似,如果我们能事先过滤掉

质数倍数,那么不是能过滤掉很多合数了吗,而对于该质数+1,

无非是两种情况,其一是它是被过滤掉的合数,其二是它是质数,

否则它应该在之前过滤掉的啊!!而我们只要在过滤的过程中,

把遇到的不能过滤的统计起来,不就是我们所求的质数吗?

这样一来,时间性能不是能更进一步优化了吗?对,但是要事先

过滤掉这么多的合数,并将其行为记录下来,就要消耗极大的

空间了,这就是典型的空间换时间!!

于是,我写的算法3便诞生了,如下:

int compuPrimeN(int maxNum)
{
	//算法3
	//用bool型大数组来记录,true为素数,false为偶数
	//因为求素数个数,所以前两个可以忽略.
	bool* bArray = new bool[maxNum + 1];
	for (int i = 2; i <= maxNum; i++)
		bArray[i] = true;

	int iNum = 0;
	for (int i = 2; i <= maxNum; i++)
	{
		//替换筛子后面的合数为false
		if (bArray[i])
		{
			iNum++;
			for (int j = i + i; j <= maxNum; j += i)
			{
				bArray[j] = false;
			}
		}
	}
	delete bArray;
	return iNum;
}

运行后如图:

哇!没想到算法的时间竟然能够优化如此快速!!但是,好像耗费的空间

存储有点多,仅用bool型的数组记录似乎有点浪费,能不能在每个bit上用0或1

来代替记录呢?

于是,我又写了下面的算法:

int compuPrimeN(int maxNum)
{
	//算法4
	//用每个位0或1来分别表示合数和素数
	//好处是内存空间利用最大化
	int size = maxNum % 8 == 0 ? maxNum / 8 : maxNum / 8 + 1;
	unsigned char* array = new unsigned char[size];
	for (int i = 0; i < size; i++)
		array[i] = 127;

	int iNum = 0, iBit = 0, index = 0;
	for (int i = 2; i <= maxNum; i++)
	{
		index = i / 8;
		(iBit = i % 8) == 0 ? iBit = 7, index-- : iBit--;

		if (array[index] & (1 << iBit))
		{
			iNum++;
			for (int j = i + i; j <= maxNum; j += i)
			{
				index = j / 8;
				(iBit = j % 8) == 0 ? iBit = 7, index-- : iBit--;
				array[index] = array[index] & (~(1 << iBit));
			}
		}
	}
	delete array;
	return iNum;
}

运行结果如图:

虽然由于二进制的计算使其在时间性能上比算法3要慢上那么一点,

但是换做bit来记录素数或合数,却是让空间存储变为了原来的1/8,

其好处是不言而喻的,如果没有内存空间问题,那么用算法3也是

无可厚非的,如果对内存空间要求比较严格,那么算法2才是最佳

首选。

总结:

在思考和编码中,我深深的体会到了,算法优化的重要性,而要想成为

一个优秀的程序员,那么就必须明白,算法是程序的灵魂!!

时间: 2024-10-07 18:08:06

用算法求N(N>=3)之内素数的个数的相关文章

[算法]求若干个线段的最大重合个数

题目描述: 给出若干个线段的起始点和终止点,求这些线段某个位置重合的最大个数. 思路: 可以先按照右端点升序,对每条线段,若它右边的线段开始时间在该条线段之前,表示和该条线段重合.算出这条线段的最大重合数量,再找下一条. 代码: package com.darrenchan; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Test{ public stat

《挑战程序设计竞赛》 大区间内素数的个数

题意: 给一个区间边界值很大的区间,但是区间大小较小,求出该区间内所有质数个数. 知识补充: 因数枚举:分解一个数n,至于要从1 枚举到 n??√ 即可,然后把i和 n / i 当做因数加入vector 整数分解(把一个整数枚举出其质数基连乘的形式):从2开始枚举质数基,然后每次把该整数尽可能的被当前质数除去最大次数,这样该整数就会变小,极大减少枚举量.注意和map搭配使用,记录每一个质数的个数. 埃式素数筛法的复杂度是:Ologlogn看做线性也无妨. 求区间[a,b)内素数的个数,由于b的最

求n之内素数

题目描述 用筛法求之N内的素数. 输入 N 输出 0-N的素数 样例输入 100 样例输出 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 提示 数组大小动态定义?函数? 来源 #include <stdio.h> int main() {     int n;     int i,j;     int o;     scanf("%d",&n);     for(i=2;

【算法】欧几里德算法--求最大公约数

预备知识 因子(除数) 如果有整数 n,a,b .a和b都不为0 ,且 有 n = a*b ,则说a(或者b,以下省略说明)为n的一个因子,或者说a能整除n. 特别的:任何非0整数都是0的因子,所以一般我们不会去求0的因子. 如:3 的因子有  1, -1 ,  3 ,  -3 .然而我们一般只考虑正数因子,因为负数因子和正数因此没有本质上的区别,只是符号不同而已. 素数:素数(也加叫质数)的定义是,如果整数p的因子 只有 ±1 和   ±p,则它就是素数 .特别的:0 和1既不是素数,也不是合

我的Java开发学习之旅------&gt;求N内所有的素数

一.素数的概念 质数(prime number)又称素数,有无限个.一个大于1的自然数,除了1和它本身外,不能被其他自然数(质数)整除,换句话说就是该数除了1和它本身以外不再有其他的因数:否则称为合数. 根据算术基本定理,每一个比1大的整数,要么本身是一个质数,要么可以写成一系列质数的乘积:而且如果不考虑这些质数在乘积中的顺序,那么写出来的形式是唯一的.最小的质数是2 二.算法 算法1. 开根号法:如果一个数(>2),对这个数求平方根,如果这个数能被这个数的平方根到2之间的任何一个(只要有一个就

如何防范算法求逆

假如您不幸遇到对Win32应用环境有足够了解的对手,以至于您的软件最终还是被凶悍的调试器任意蹂躏.但是您还远没有被打败,如果反调试技术(Anti-Debug)作为软件保护的第一道防线已经失守,您的对手只不过是掌握了一大堆汇编代码而已,毕竟代码和算法之间还是有相当距离的,所以您还有第二道防线可守--抗分析.在这道防线里,您有很多办法可以限制破解者掌握您的加密算法,从而阻止注册机或者破解补丁的出现. 一.前言 软件保护的目的是只向合法用户提供完整的功能,所以软件保护必然要包括验证用户合法性的环节,而

python脚本11_求10万以内所有素数

#求10万以内所有素数 num = int(input(">>>")) strs = '' for i in range(2,num): for c in range(2,int(i**0.5)+1): if i%c == 0: break else: strs += str(i)+' ' print(strs) 方法2: print(2) for i in range(3,100001,2): if i>10 and i%10 == 5: continue e

poj2187 求平面最远点对,garham_scan算法求凸包

poj2187 求平面最远点对,garham_scan算法求凸包 Beauty Contest Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 29666   Accepted: 9180 Description Bessie, Farmer John's prize cow, has just won first place in a bovine beauty contest, earning the title 'M

EM算法求高斯混合模型参数估计——Python实现

EM算法一般表述: 当有部分数据缺失或者无法观察到时,EM算法提供了一个高效的迭代程序用来计算这些数据的最大似然估计.在每一步迭代分为两个步骤:期望(Expectation)步骤和最大化(Maximization)步骤,因此称为EM算法. 假设全部数据Z是由可观测到的样本X={X1, X2,--, Xn}和不可观测到的样本Z={Z1, Z2,--, Zn}组成的,则Y = X∪Z.EM算法通过搜寻使全部数据的似然函数Log(L(Z; h))的期望值最大来寻找极大似然估计,注意此处的h不是一个变量