素数筛法(素筛)

素数筛法的证明 :(大佬们要耐心读完呦,我辛辛苦苦总结的呢,记得点个好评呀)
说到素数的定义,大家从小学就都知道,一个数除了1和它本身没有额外的质因数称为素数,那么我们如何将1~10000中所有的素数用最快的方法筛选出来呢,下面就是一步步优化的过程:

1. 首先是最直接最简明的方法:
我从外部得到了一个数n,我只需要按照定义判断它是不是只能被1它本身整除就行了,下面是相应的一个计算素数的函数:

#include
using namespace std;
bool prime(int n)				//函数返回值是布尔型的,返回值只有True或False不懂的可以百度一下 
{
	int flag = 0;				//用来判断除了被1和它本身,在for循环中是否被能其他的数整除 
	int m = n;
	for(int i=2;i0相当于真 
	else return true;
}
int main()
{
	int n;
	while(cin>>n)
	{
		if(prime(n)) cout<<"是素数!"<

这个代码虽然能够判断一个数是不是为素数,但是运算效率十分的低,在一些比赛中很容易就运行超时,下面我们来优化一下。


2.对于一个数n,很明显,所有n的倍数都不是素数,如对于数 2 ~~ 22 ,23……2*n都不是素数,因此求1n的素数,那么将1n所有数的倍数剔除,那么剩下的即是素数,不是素数标记为 1(读者可能就很好奇了,怎么标记为1呢,别急,现在就告诉你)
说到将一个数标记为1,是这样的,我们先用布尔类型开一个容量为100000(十万)的数组,即bool str[100000],当然我们可以给它开成bool str[100010],多开大10是为了防止鲁棒性(即程序在运行较复杂的问题出现的多占用内存问题,防止报错,这可是北航的小哥哥给我解释的哦),上面已经介绍过,布尔类型返回值只有TrueFalse,所以这个数组里只能保存0非0,这样我们就能用1标记非素数了,即下标不是素数的我们就能给它赋值为1,比如,4不是素数,那么str[4] = 1,6不是素数,那么str[6] = 1,是不是理解了一些呢?那么没被标记的都是0了吗?当然是的,如果我把这个数组定义在了开头,那么这个数组中刚开始每个下标对应的数都为0
下面是相应的代码:

#include
using namespace std;
bool str[100010]; 					//**开始定义一个全局变量数组,每个下表对应的值默认为0** 
void prime()						//这个函数可以将1~100010内的所有素数都找出来,所以在main()函数开头执行一遍就行了 
{
	str[1] = 1;		
	for(int i=2;i<100010;i++)
	{
		for(int j=2*i;j<100010;j+=i)
		str[j] = 1;					//某个数的倍数肯定不是素数 
	}
}
int main()
{
	prime(); 						 //执行程序开始的打表操作 
	int n;							
	while(cin>>n)
	{
		if(str[n]) cout<<"不是素数!"<

这个方法就比第一个那个直接的方法快很多了,在刚开始打好表(就是将所有素数查找出来),然后在后面的操作中输入n,就能直接判断出是不是素数,但是这种方法只能做一部分题,在测试数据比较大并且多时,还是很容易时间超限的,下面我们来介绍一种更快,更高端的素筛方法。


3.我们将上面的程序来做进一步的优化:
如果我们想找1~10000内的素数,那么我们只需要在上面第二种方法中的那个prime()函数中的第一层循环,循环到根号下10000,即循环到i = 100时即可,因为从100 ~ 10000中所有的非素数都是1~100中数的倍数,如果你不信,我们可以找几个数试试啊,首先你要先找到一个100 ~ 10000中的非素数,那么它除了1和它本身,应该还会有1个或多个因子,我们假设其中一个因子在10010000中间,那么与其对应的另外一个因子一定在1100中间否则两个数相乘会大于10000,比如9999,他的一个因子是101,那么对应的另外一个因子就是99(我说小于100吧,还不信,哼)那么我们的第一层循环就缩短了好长时间(是吧,嘻嘻)。
现在呢,我们可以在第一层循环和第二层循环中间做一个优化,即在第一层和第二层循环中间设置一个素数判断,判断第一层循环传递到第二层循环里的i是不是素数,如果不是素数,那么就跳过,比如我在i等于2时就已经能够判断2的倍数4不是素数了,那么循环到i=4的时候我就没有必要再将4的倍数都标记为1,因为4的倍数都是2的倍数,再标记一遍的话就重复标记了,所以没那个必要,好了在这个地方我们又进行了一次优化,代码运行效率又提高了点。
现在进行第三次进化(哈哈),在第二层的循环体中我们可以将定义的int j = 2 * i改为int j = i * i,读者是不是有点小懵逼呢(别急,玩代码的可不能急哦,坚持看下去),好吧,*我们假设刚开始i = 2的时候,是不是把 i 的倍数都标记成1了?是的吧,那么2 * 3肯定也被标记成1了,所以当 i 运行到等于3的时候就没必要在把3 * 2给标记一遍了是不?是的吧,要不然就又重复标记了,同理,在 i = 4的时候,那么4 * 2 和4 3是不是在i = 2 ,i = 3 的时候分别被标记过了(嘻嘻,是不是好好玩)?所以在循环第二层的时候从j = 4 4(int j = ii)开始就好了,到此我们把第二种素数判定优化了三个地方,读者是不是都还记得呢?这是我辛辛苦苦总结的,喜欢就给我点个好评呗,谢谢!( ̄∀ ̄)!
下面是优化后的代码:

#include
using namespace std;
bool str[100010]; 					//开始定义一个全局变量数组 
void prime()						//这个函数可以将1~100010内的所有素数都找出来,所以在main()函数开头执行一遍就行了 
{
	str[1] = 1;						//希望读者能将这一块跟第二种方法进行一个比较,便于理解 
	for(int i=2;i*i<=100010;i++)
	{
		if(!str[i]) 
		{
			for(int j=i*i;j<=100010;j+=i)
			str[j] = 1;	
		}				
	}
}
int main()
{
	prime(); 						 //执行程序开始的打表操作 
	int n;							
	while(cin>>n)
	{
		if(str[n]) cout<<"不是素数!"<

你可能感兴趣的:(算法理论)