素数,又称质数,是一个大于一的自然数,除了1和它本身外,不能被其他的自然数整除的数被称为质数,否则成为合数
1不属于质数也不属于合数
目录
下面给出一道例题并列出了三种实现算法,以此介绍关于素数的算法,其中第三种时间复杂度最小
方法一:
单独判断1,然后从2到n - 1, 判断是否存在能被n整除的数,既
(n%i == 0, 2 <= i <= n - 1),如果有就不是素数,否则为素数。
代码中附上程序运行时间,一1000,000为例,数字太小的话无发凸显代码优势。以此判断程序的时间复杂度。由于机器原因时间复杂度的比较不是太明显。但第三种算法要远远优于其他两种方法。
代码:
#include<cstdio>
#include<ctime>
using namespace std;
int main()
{
int n,i,j;
while(~scanf("%d",&n))//输入次数小于或等于30
{
int sum=1;
if(n==1)
{
sum=0;
}
for(i=3;i<=n;i+=2)//由于偶数除了2之外倒不是素数所以只需要判定奇数即可
{
for(j=2;j<i;j++)
{
if(i%j==0)
{
break;
}
}
if(j==i)//当j==i时此回合判定结束
{
sum++;
}
}
printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);//获取程序运行的时间
printf("%d\n",sum);
}
return 0;
}
运行结果:
1000000数量级未带动
100000数量级运行时间在6秒左右明显处劣势
方法二:
方法二其实都是对上述方法进行优化,可以很好地降低时间复杂度。利用以下结论:对正整数n,如果用2到√n(根号n)之间的所有整数去除,均无法整除,则n为质数。
代码:
#include<cstdio>
#include<cmath>
#include<ctime>
using namespace std;
int main()
{
int n,i,j;
int temp=1;
while(~scanf("%d",&n))//输入次数小于或等于30
{
int sum = 1;
if(n==1)
{
sum=0;
}
for( i = 3; i <= n; i+=2)
{//因为偶数除了2之外都不是素数(质数),所以只需判断奇数,从3开始每次+2
for(j = 2; j <= (int)sqrt(i); j++)//利用上述结论判断
{
if(i%j == 0)break;
}
if(j > (int)sqrt(i))
{
sum++;
}
}
printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);//获取程序运行时间
printf("%d\n",sum);
}
return 0;
}
运行结果:
方法三:
方法三使用到孪生素数的一些特点和结论
孪生素数:孪生素数指的是间隔为 2 的相邻素数。
1.当n >= 6, n - 1 和 n + 1 为孪生素数,那么n 一定是6的倍数。
Proof :
∵ n - 1, n + 1是素数,也即 n - 1 和 n + 1是 奇数
∴ n 是 偶数,n 是 2 的倍数。
设 n 不是 3 的倍数,即 n = 3k + 1 或 n = 3k + 2。
(i)当 n = 3k + 1 时,那么 n - 1 = 3k,已经与 n - 1 是素数矛盾。
(ii)当 n = 3k + 2 时, 那么 n +1=3(k + 1),已经与 n + 1是素数矛盾。
综上所述,n 是 3 的倍数。
∵n既是2的倍数,又是3的倍数
∴n 是6的倍数。
推论1:当 x >= 1, (6x - 1)或 (6x + 1)不是素数时,它们的质因子不包括2和3的倍数,因为2(3x) - 1, 3(2x) - 1,2(3x) + 1, 3(2x) + 1。
2.素数分布规律:当n >= 5时,如果n为素数,那么n % 6 = 1 || n % 6 = 5,即n一定出现在6x(x≥1)两侧。
(就是说大于等于5的素数一定是分布在6倍数的左右两侧,但在6倍数左右两侧的数不一定是素数)
Proof:
可以把6x附近的数用以下方式表示:
……(6x - 1), 6x, 6x+1, 2(3x+1), 3(2x+1), 2(3x +2), 6x + 5, 6(x+1)……
不在6x两侧的数为: 2(3x+1), 3(2x+1), 2(3x +2),它们不是素数,所以素数出现在6x的两侧。
有了以上的理论基础,我们可以对方法2进一步地优化,首先不在6x左右的数2,3单独处理,接下来只要判断 6x两侧的数是否为素数。因为合数总是可以写成素数的乘积,那么我们直接用n去除以质数就可以达到很好地优化目的。而质数一定是 6x 两侧的数(推论一已证明了当 n >= 5 时,n不是素数时,n 不含质因子2,3) , 6x 两侧的数是大于素数的集合,因此可以用n 除以 6x 两侧的数即if(n % i == 0 || n % (i +2) == 0)时,不是素数。
代码:
#include<cstdio>
#include<cmath>
#include<ctime>
using namespace std;
int main()
{
int n,i,j;
while(~scanf("%d",&n))//输入次数小于或等于30
{
int sum = 1;//已经将2单独处理
if(i == 1)//3单独处理
{
sum=0;
}
int flag = 0;
for(int i = 3; i <= n; i+=2)
{
if(i == 3)//3单独处理
{
sum++;
}
if(i % 6 != 1 && i % 6 !=5)
{
continue;//不是6x两侧的数不是素数
}
/*
上面的判定根据孪生素数尽可能的减小了需判断数的数量,极大地节省了程序运行的时间
经过筛选,将筛选后剩余的数,通过普通方法在进行判断
*/
for( j = 5; j <= (int)sqrt(i); j+=6)//对6x两侧的数进行判断
{
if(i%j == 0 || i%(j + 2) ==0)
{
flag = 1;
break;
}
}
if(flag == 1)
{//如果判定不为素数,flag被赋值为1;i+2也不为素数 ,因此判断flag是否等于1进行拦截
flag = 0;
continue;
}
sum++;
}
printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);//获取程序运行的时间
printf("%d\n",sum);
}
return 0;
}
运行结果: