素数(质数)判断方法

https://blog.csdn.net/songyunli1111/article/details/78690447  ->通俗易懂的解释

标准版:大部分人都知道的比较快的方法:判断从2到sqrt(n)是否存在其约数,时间复杂度O(sqrt(n))

高配版:判断2之后,就可以判断从3到sqrt(n)之间的奇数了,无需再判断之间的偶数,时间复杂度O(sqrt(n)/2)

 

尊享版:

首先看一个关于质数分布的规律:大于等于5的质数一定和6的倍数相邻。例如5和7,11和13,17和19等等;

证明:令x≥1,将大于等于5的自然数表示如下:

··· 6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,6(x+1),6(x+1)+1 ···

可以看到,不和6的倍数相邻的数为6x+2,6x+3,6x+4,由于2(3x+1),3(2x+1),2(3x+2),所以它们一定不是素数,再除去6x本身,显然,素数要出现只可能出现在6x的相邻两侧。因此在5到sqrt(n)中每6个数只判断2个,时间复杂度O(sqrt(n)/3)。

在高配版和尊享版中,都是一个剪枝的思想,高配版中裁剪了不必要的偶数,尊享版中裁剪了不和6的倍数相邻的数,虽然都没有降低时间复杂度的阶数,但都一定程度上加快了判断的速度。

在此给出尊享版C++代码:

#include <iostream>
#include <math.h>
using namespace std;
int isPrime(int n)
{	//返回1表示判断为质数,0为非质数,在此没有进行输入异常检测
	float n_sqrt;
	if(n==2 || n==3) return 1;
	if(n%6!=1 && n%6!=5) return 0;
	n_sqrt=floor(sqrt((float)n));// sqrt开平方根 floor向下取整
	for(int i=5;i<=n_sqrt;i+=6)
	{
	    if(n%(i)==0 | n%(i+2)==0) return 0;
	}
        return 1;
} 
int main()
{
	int flag;
	flag=isPrime(37);
	cout<<flag<<endl;
	return 0;
}
--------------------- 
版权声明:本文为CSDN博主「lyshello123」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/songyunli1111/article/details/78690447


--------------------- 
版权声明:本文为CSDN博主「lyshello123」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/songyunli1111/article/details/78690447

 

 

素数(质数)的判断在算法问题中经常遇到,这里小结几种常用的判断方法。

素数(质数)的定义
首先,我们来看一下素数(质数)的定义:质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。

方法一
我们可以从它的定义得到判断素数的 第一个方法: 从 2 22 到 n−1 n - 1n−1, 判断是否存在能被n整除的数,既(n%i==0,2&lt;=i&lt;=n−1) (n \% i == 0, 2 &lt;= i &lt;= n - 1)(n%i==0,2<=i<=n−1),如果有就不是素数,否则为素数。

(这里为了比较几种算法的性能,用计算出1000,000中有多少个素数所需的时间作为性能比较的参考。附上本篇博文所有程序的运行环境(机房哈哈):系统windows xp, cpu:E5200,2.50GHZ, 0.99GB内存,IDE:codeblocks)

首先,可以先作一个小的优化,既除2以外,只需判断所有的奇数是否是素数。

//素数判断方法1
#include<stdio.h>
#include<time.h>
#define Max 1000000
int main()
{
    int sum = 1;//2单独处理,sum为素数的个数
    for(int i = 3; i <= Max; i+=2)
    {//因为偶数除了2之外都不是素数(质数),所以只需判断奇数,从3开始每次+2
        int j;
        for(j = 2; j < i; j++)//判断
            if(i%j == 0)break;
        if(j == i)
            sum++;
    }
    printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);//获取程序运行的时间
    printf("%d",sum);
    return 0;
}

程序的时间复杂度为O(n^2)。
运行结果

结果表明n的数量级太大时,不宜采用该方法。
同时也可以得到结论1000,000 1000,0001000,000中有78498 7849878498个素数。

接下来的方法其实都是对上述方法进行优化,可以很好地降低时间复杂度。
利用以下结论:对正整数n nn,如果用2到 n−−√ \sqrt{n} 
n
​    
 之间的所有整数去除,均无法整除,则n nn为质数。

方法二
//素数判断方法2
#include<stdio.h>
#include<time.h>
#include<math.h>
#define Max 1000000
int main()
{
    int sum = 1;
    for(int i = 3; i <= Max; i+=2)
    {//因为偶数除了2之外都不是素数(质数),所以只需判断奇数,从3开始每次+2
        int j;
        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",sum);
    return 0;
}

程序的时间复杂度为O(n−−√ \sqrt{n} 
n
​    
 )。
运行结果

可以看到程序的运行时间已经大幅度地降低。

接下来的方法需要利用到孪生素数的一些特点和结论。

孪生素数
孪生素数:孪生素数指的是间隔为 2 22 的相邻素数。
1.当n>=6 n &gt;= 6n>=6, n−1 n - 1n−1 和 n+1 n + 1n+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的倍数。 \begin{aligned}&amp;∵ n - 1, n + 1是素数,也即 n - 1 和 n + 1是 奇数\\ &amp;∴n 是 偶数,n 是 2 的倍数。\\&amp;设 n 不是 3 的倍数,即 n = 3k + 1 或 n = 3k + 2。\\&amp;(i)当 n = 3k + 1 时,那么 n - 1 = 3k,已经与 n - 1 是素数矛盾。\\&amp;(ii)当 n = 3k + 2 时, 那么 n +1=3(k + 1),已经与 n + 1是素数矛盾。\\&amp;综上所述,n 是 3 的倍数。\\&amp;∵n既是2的倍数,又是3的倍数\\&amp;∴n 是6的倍数。\\\end{aligned}
​    
  
∵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 x &gt;= 1x>=1, (6x−1) (6x - 1)(6x−1)或 (6x+1) (6x + 1)(6x+1)不是素数时,它们的质因子不包括2和3的倍数,因为2(3x)−1,3(2x)−1,2(3x)+1,3(2x)+1 2(3x) - 1, 3(2x) - 1,2(3x) + 1, 3(2x) + 12(3x)−1,3(2x)−1,2(3x)+1,3(2x)+1。

2.素数的分布规律:当n>=5 n &gt;= 5n>=5时,如果n为素数,那么n%6=1∣∣n%6=5 n \% 6 = 1 || n \% 6 = 5n%6=1∣∣n%6=5,即n一定出现在6x(x≥1) 6x(x≥1)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的两侧。 \begin{aligned}&amp;可以把6x附近的数用以下方式表示:\\&amp;……(6x - 1), 6x, 6x+1, 2(3x+1), 3(2x+1), 2(3x +2), 6x + 5, 6(x+1)……\\&amp;不在6x两侧的数为: 2(3x+1), 3(2x+1), 2(3x +2),它们不是素数,所以素数出现在6x的两侧。\\\end{aligned}
​    
  
可以把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 6x6x左右的数2,3 2,32,3单独处理,接下来只要判断6x 6x6x两侧的数是否为素数。因为合数总是可以写成素数的乘积,那么我们直接用n去除以质数就可以达到很好地优化目的。而质数一定是 6x 6x6x 两侧的数(推论一已证明了当n>=5 n &gt;= 5n>=5时,n不是素数时,n 不含质因子2,3) , 6x 6x6x 两侧的数是大于素数的集合,因此可以用 n nn 除以 6x 6x6x 两侧的数即if(n % i == 0 || n % (i + 2) == 0)时,不是素数。

方法三
//素数判断方法3
#include<stdio.h>
#include<time.h>
#include<math.h>
#define Max 1000000
using namespace std;
int main()
{
    int sum = 1;//已经将2单独处理
    int flag = 0;
    for(int i = 3; i <= Max; i+=2)
    {
        if(i == 3)//3单独处理
            sum++;
         
        if(i % 6 != 1 && i % 6 !=5)
            continue;//不是6x两侧的数不是素数
        
        for(int j = 5; j <= (int)sqrt(i); j+=6)//对6x两侧的数进行判断
            if(i%j == 0 || i%(j + 2) ==0)
            {
                flag = 1;
                break;
            }
            if(flag == 1)
            {
                flag = 0;
                continue;
            }
        sum++;
    }
    printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);
    printf("%d",sum);
    return 0;
}

运行结果:

方法3运行结果表明比方法2快速了许多倍,已经是很高效的算法了。

方法四:普通筛选法
更新日记:2018/1/31增加普通筛选法
比上面几种方法更快,也比较容易理解。
思想: 一个素数的倍数都不是素数。
时间复杂度: O(nloglogn)

#include<iostream>
#include<vector>
using namespace std;
const int maxt = 1000000;
vector<bool>prime;
int main()
{
    prime.resize(maxt,1);
    prime[0] = prime[1] = 0;//1是素数,0是非素数
    for(int i = 2; i*i <= prime.size(); i++)
    {
        if(prime[i] == 1)
        for(int j = i*2; j <= prime.size(); j += i)
        {
            prime[j] =  0;
        }
    }
    return 0;
}

--------------------- 
版权声明:本文为CSDN博主「Cqh_i」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CPOHUI/article/details/78667490

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值