首先还是一样,让我们看看什么是素数。素数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。

因此我们从简单的开始,例:找出100->200之间的所有素数。

同样我们需要

  1. 遍历100->200的所有自然数,这次我们使用for循环来实现遍历。
  2. 判断是否因数只有1和其本身,我们只要循环判断该数在1到它本身没有其他因数即可
  3. 我们看到使用了两次循环,因此我们应该循环嵌套
  4. 当然为了直观看出代码的效率我们使用count来计数(count1为运行次数,count2为有效次数)

由此我们写出如下代码:

#include<stdio.h>
int main ()
{
int i = 0,count1,count2;							//count计数器
  for(i=100;i<=200;i++)								//i的遍历
  {
    int j;
    for(j=2;j<=i;j++)									//因子j
    {
    	count1++;												//内循环一次计一次
    if(i%j == 0)											//若整除我们则确定为不是素数
		{
	      break;												//直接break跳出减少次数
		}
    }
    if(i == j)												//若j到i时还没整除则为素数,打印i,计为一次有效
	  {
	  	printf("%d\t",i);
	  	count2++;
	  }
  }
  printf("\n");
  printf("一共运行了%d次\n",count1);
  printf("一共有素数%d个\n",count2);
  return 0;
}

运行结果如下:

101     103     107     109     113     127     131     137     139     149     151     157     163     167     173    179      181     191     193     197     199
一共运行了3314次
一共有素数21个

--------------------------------
Process exited after 0.09238 seconds with return value 0
请按任意键继续. . .

我们看到这个效率并不是很高,那是否可以优化一下算法呢?

我们思考知道偶数一定不是素数因此得出:

#include<stdio.h>
int main ()
{
int i = 0,count1,count2;
  for(i=101;i<=200;i+=2)					//我们这里改变i从首个奇数101开始,每次循环+2,跳过偶数判断
  {
    int j;
    for(j=2;j<=i;j++)
    {
    	count1++;
    if(i%j == 0)
		{
	      break;
		}
    }
    if(i == j)
	  {
	  	printf("%d\t",i);
	  	count2++;
	  }
  }
  printf("\n");
  printf("一共运行了%d次\n",count1);
  printf("一共有素数%d个\n",count2);
  return 0;
}

结果如下:

101     103     107     109     113     127     131     137     139     149     151     157     163     167     173    179      181     191     193     197     199
一共运行了3263次
一共有素数21个

--------------------------------
Process exited after 0.06562 seconds with return value 0
请按任意键继续. . .

我们看到结果一样,但运行次数确实减少了,达到了优化的目的。

但这个优化是否还可以近一步?

我们知道一个合数一定可以写成两个数的积(即:i=a*b),由此知道a,b中一定有一个数小于根号i,

另一个大于根号i。

所有我们判断i是否为素数,只需要判断i在根号i范围内没有因子即可

我们在代码中引入数学库函数,代码如下:

#include<stdio.h>
#include<math.h>
int main ()
{
int i = 0,count1,count2;
  for(i=101;i<=200;i+=2)
  {
    int j;
    for(j=2;j<=sqrt(i);j++)				//循环范围改变
    {
    	count1++;
    if(i%j == 0)
		{
	      break;
		}
    }
    if(j>sqrt(i))									//判断条件改变
	  {
	  	printf("%d\t",i);
	  	count2++;
	  }
  }
  printf("\n");
  printf("一共运行了%d次\n",count1);
  printf("一共有素数%d个\n",count2);
  return 0;
}

运行结果如下:

101     103     107     109     113     127     131     137     139     149     151     157     163     167     173    179      181     191     193     197     199
一共运行了367次
一共有素数21个

--------------------------------
Process exited after 0.08626 seconds with return value 0
请按任意键继续. . .

同样,素数个数一样,但运行极大的减少了。

如果我们要从1到20000范围查找素数,比较三种方法的效率高低,得出

一个有2262个素数

方法1:一共运行了21269832次

方法2:一共运行了21259833次

方法3:一共运行了291302次

确实可以看出方法3的效率

以上方法我们通常叫做试除法(一个一个除再判断)

接下来我们还有筛选法

筛选法也叫埃氏筛选法

筛选法和试除法其实有着本质上的区别,试除法是判断每个数是不是素数来达到目的;而筛选法不是,筛选法是将不是素数的数全部去除,然后得到余下的数来达到目的。

首先1不是质数也不是合数,所以要划去;接着2是公认最小的质数,所以要保留下来,再把所有2的倍数去掉;然后接下来遇到的第一数不会是2的倍数,所以它必然只可能被1和他自身整除,为素数,而2后面第一个没有被划去的数是3,所以要保留素数3,再把所有3的倍数去掉;接着往复之前的判断,剩下的那些大于3的数里面,最小的是5,所以5也是质数,再去掉五的倍数......

  上述过程不断重复,就可以把某个范围内的合数全都除去(就像被筛子筛掉一样),剩下的就是质数了。如果理解还不怎么清晰,来看看直观地体现出筛法的工作图,如下所示:

求解素数的不同境界_试除法

下面我们来看看如何实现。

看图我们发现

  1. 先找到一个最小的素数,再其排除所有的倍数(这里我们使用for循环排除)
  2. 然后我们思考如何才能使排除的数不再循环判断(这里我们可以用判断语句,使排除的数不符合条件,从而达到不循环的目的)
  3. if判断条件有0为假的特点,所以我们想到给所有的被排除数赋值为0,这里使用数组即可

我们还是以100->200为例再原有代码的基础上更改

从而有如下代码:

#include<stdio.h>
int main()
{
	int i=0,count1=0,count2=0,arr[201] = {0};
	for(i=2;i<=200;i++)						//给数组赋初始值
		arr[i]=1;										//这里初值不为0即可,使开始所有数符合循环条件
	for(i=2;i<=200;i++)
	{															
		if(arr[i])									//这里注意,因为第一次是2进来,且后面排除的数不会进来
		{														//因此能进来的一定是素数
			int j=0;
			if(i>=100 && i<=200)			//这里判断100->200之间,然后下面打印
			{
				printf("%d\t",i);				
				count2++;								//计素数
			}
			for(j=i+i;j<=200;j+=i)		//开始排除倍数
			{
				arr[j] = 0;							//使被排除的数不符合判断条件
				count1++;								//分析出排除次数等于运行次数
			}
		}	
	}
	printf("\n");
 	printf("一共运行了%d次\n",count1);
  	printf("一共有素数%d个\n",count2);
return 0;
}

运行结果如下:

101     103     107     109     113     127     131     137     139     149     151     157     163     167     173    179      181     191     193     197     199
一共运行了323次
一共有素数21个

--------------------------------
Process exited after 0.05069 seconds with return value 0
请按任意键继续. . .

运行结果看出,运行效率确实更好了,数据越多效果越明显。

我们分析运行过程,发现还可以更优化,例如:6=2*3;这里的2我们排除了一次后3又排除了一次,

相当于6排查了两次,还可以优化一下,使每个数只排查一遍

即:我们排查2时从2*2开始排查,排查3时从3*3开始排查,而2*3在排查2时已经查过了;同理排查4时从4*4开始,2*4和3*4在2和3时查过,如此我们每次只需要从i*i开始排查即可

于是得到代码如下:

#include<stdio.h>
int main()
{
	int i=0,count1=0,count2=0,arr[201] = {0};
	for(i=2;i<=200;i++)
		arr[i]=1;
	for(i=2;i<=200;i++)
	{
		if(arr[i])
		{
			int j=0;
			if(i>=100 && i<=200)
			{
				printf("%d\t",i);
				count2++;
			}
			for(j=i*i;j<=200;j+=i)					//从i*i开始
			{
				arr[j] = 0;
				count1++;
			}
		}	
	}
	printf("\n");
 	printf("一共运行了%d次\n",count1);
  	printf("一共有素数%d个\n",count2);
return 0;
}

运行结果:

101     103     107     109     113     127     131     137     139     149     151     157     163     167     173    179      181     191     193     197     199
一共运行了232次
一共有素数21个

--------------------------------
Process exited after 0.05693 seconds with return value 0
请按任意键继续. . .

然后我们来查小于20000的素数,比较几种方法的效率;

方法4:一共运行了47864次

方法5:一共运行了35485次

看出来了吗,筛选法只用了几万次,而试除法运行了几十万次,甚至几千万次

在庞大的基数下筛选法的效率就体现出来了。

然而我们还会发现16=4*4=2*8,我们还是有一些数据排查过两次,类似的完全平方数例如36,64等

我们是否还能再优化呢?找到这些数和代码之间的微妙联系,试着分析可行性吧。