素数判断的几种方法代码实现及其复杂度分析
素数判断的几种方法代码实现及其复杂度分析
原文链接:http://blog.csdn.net/infinitezhen/article/details/8961964
一、 朴素判断素数
根据素数的定义,约数只有1和它本身的整数称为素数,假设一个整数为n,于是最朴素的判断n是否为素数的方法就是从2到n-1都枚举一遍,判断是否存在能整除n的整数,如果都不能则n为素数。
代码实现如下:
- bool Brute_Force(int n)
- {
- for (int i=2; i<=n-1; i++)
- if (n%i==0) return false;
- return true;
- }
bool Brute_Force(int n)
{for (int i=2; i<=n-1; i++)if (n%i==0) return false;return true;
}
此函数返回true则说明n为素数,反之不是。
很容易发现,这种方法判断素数,对于一个整数n,需要n-2次判断,时间复杂度为O(n)的。在n非常大或者测试量很大时,这种方法显然是不可取的。
二、 改进朴素判断素数
对于一个小于n的整数x,如果n不能整除x,则n必然不能整除n/x。反之相同。所以我们按照素数定义来判断素数时,可以进行一个较为明显的优化。即我们只需从2枚举到√n即可。因为在判断2的同时也判断了n/2……以此类推,到√n时就把2到n-1的数都判断过了。
代码实现如下:
- bool Brute_Force2(int n)
- {
- for (int i=2; (__int64)i*i<=n; i++)
- if (n%i==0)
- return false;
- return true;
- }
bool Brute_Force2(int n)
{for (int i=2; (__int64)i*i<=n; i++)if (n%i==0)return false;return true;
}
这里使用i*i<=n来取代i<=√n
是为了避免是用sqrt()函数,其消耗时间很大,在大量数据测试中时间消耗很明显。同时强制转换i成__int64类型是为了防止i*i在int范围内溢出。此算法的时间复杂度也很容易得出,对于一个整数n,需要测试√n-1次,所以本算法的时间复杂度为O(√n)的。
三、 标准的爱拉托逊斯筛选法
爱拉托逊斯筛选法(以下简称筛法),是一种高效的判断素数的方法。能够一次性的筛选出某个区间的素数。其算法原理本质还是充分利用了素数的定义,即素数的约数只有1和它本身。如果某个数m是另一个数n的倍数,则说明m肯定不是素数。所以我们只要在某个范围内,将已知素数的所有倍数都筛去,剩下的肯定是素数。因为只有它不是其他数的倍数(1和本身除外)。
具体做法是:先把N个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一直做下去,就会把不超过N的全部合数都筛掉,留下的就是不超过N的全部质数。因为希腊人是把数写在涂腊的板上,每要划去一个数,就在上面记以小点,寻求质数的工作完毕后,这许多小点就像一个筛子,所以就把埃拉托斯特尼的方法叫做“埃拉托斯特尼筛”,简称“筛法”。(另一种解释是当时的数写在纸草上,每 要划去一个数,就把这个数挖去,寻求质数的工作完毕后,这许多小洞就像一个筛子。)
代码实现如下:
- #define MAX 10007
- bool isprime[MAX];
- void TheSieveofEratosthees()
- {
- int i,j;
- for (i=2; i<MAX; i++)
- isprime[i]=1;
- for (i=2; i<MAX; i++)
- {
- if (isprime[i])
- for (j=i+i; j<MAX; j+=i)
- isprime[j]=0;
- }
- }
#define MAX 10007
bool isprime[MAX];
void TheSieveofEratosthees()
{int i,j;for (i=2; i<MAX; i++)isprime[i]=1;for (i=2; i<MAX; i++){if (isprime[i])for (j=i+i; j<MAX; j+=i)isprime[j]=0;}
}
在执行完本算法后,isprime[i]=1则说明i是素数。所以本算法在执行完一遍后,就能在O(1)的时间复杂度内判断MAX以内的任意数是否为素数。所以整个算法的时间消耗都在筛法的效率上。乍看筛法的时间复杂度貌似是O(n^2)的,但是其实不然,第二个循环中,每次递增的i,当i越来越大时,j很快就能超过M。其实筛法的实际复杂度是的,在可以测试的范围内,其实是接近线形的,虽然实际上不是。这个是筛法的精妙所在。
四、 改进的爱拉托逊斯筛选法
理论上筛法在可以测试的范围内,已经接近线性的复杂度了,对于一般的需要来说,已经没有什么必要去优化筛法了。但是为了更深入或者满足更苛刻的效率要求,标准的筛法还是有可以改进的地方的,使得筛法在常数级别上得到降低。实际上在2007年,复旦的xreborner已经将筛法改进为真正的线性时间复杂度。
法是增加了一个数组,记录已经找到的素数,通过这些已经找到的素数,来筛掉后面的数,由于每个数都能分解成质因数的形式,所以所有质因数都被筛掉后,自然不在素数列表中了。
代码实现如下:
- #define MAX 10007
- bool isprime[MAX];
- int p[MAX];
- void prime(int n)
- {
- memset(isprime, 0, sizeof isprime);
- memset(p, 0, sizeof p);
- int np = 0;
- for (int i = 2; i <= n; i++)
- {
- if (!isprime[i]) p[np++] = i;
- for (int j = 0; j < np && p[j]*i<= n; j++)
- {
- isprime[p[j]*i] = 1;
- if( i % p[j] == 0) break;
- }
- }
- }
#define MAX 10007
bool isprime[MAX];
int p[MAX];
void prime(int n)
{memset(isprime, 0, sizeof isprime);memset(p, 0, sizeof p);int np = 0;for (int i = 2; i <= n; i++){if (!isprime[i]) p[np++] = i;for (int j = 0; j < np && p[j]*i<= n; j++){isprime[p[j]*i] = 1;if( i % p[j] == 0) break;}}
}
这个算法的关键在于if(i%pr[j]== 0) break;。它使得任何一个合数,只被它最小的质因数标记过一次。所以整个算法是线性的。但考虑到log(log(100000000))还不到3,故这个线性算法其实也只有理论上的价值罢了。
五、 朴素判断+筛法
通过上面的筛法实现可以看出,用筛法直接判断素数是很有限的,筛法受内存的限制,要判断n是否为素数,需要开大小为n的bool数组。当n很大的时候,显然是不可取的。所以我们可以折中以上两种算法,将朴素判断和筛法结合在一起,使得朴素判断能得到进一步的优化。方法二中朴素判断的优化已经大大降低了复杂度。其实我们再深入理解就会发现,其实从2到√n中,也是有很多判断是没必要的,比如某个数n不能被2整除,则必然不能被4整除(其实2的倍数都不能)。所以用筛法预处理出小于√n的所有素数。这样在大量数据测试的时候效率提高很多。
代码实现如下:
- void prime(int n)
- {
- memset(isprime, 0, sizeof isprime);
- memset(p, 0, sizeof p);
- np = 0;
- for (int i = 2; i <= n; i++)
- {
- if (!isprime[i]) p[np++] = i;
- for (int j = 0; j < np && p[j]*i<= n; j++)
- {
- isprime[p[j]*i] = 1;
- if( i % p[j] == 0) break;
- }
- }
- }
- bool Brute_Force3(int n)
- {
- for (int i=0; p[i]*p[i]<=n; i++)
- if (n%p[i]==0)
- return false;
- return true;
- }
void prime(int n)
{memset(isprime, 0, sizeof isprime);memset(p, 0, sizeof p);np = 0;for (int i = 2; i <= n; i++){if (!isprime[i]) p[np++] = i;for (int j = 0; j < np && p[j]*i<= n; j++){isprime[p[j]*i] = 1;if( i % p[j] == 0) break;}}
}
bool Brute_Force3(int n)
{for (int i=0; p[i]*p[i]<=n; i++)if (n%p[i]==0)return false;return true;
}
由以上5种方法可以看出,并不是朴素算法就一定没优点,也不是高效的筛法就很完美,有时候经过深入了解,将各种已经存在的算法组合在一起也能发挥很大的效果,从而达到优化原先算法的程度。上面的算法总时间复杂度理论上也是O(√n)的,但是常数上已经得到很大的优化,效率上比原来改进的朴素快了好几十倍之多。数据范围越大,其优化效果也明显。
六、 费马素性测试
费马小定理说的是:如果p是一个素数,那么对于任意一个整数a,a p − a 能被p整除,也可以用模运算表示如下:(p是素数,a是整数)这个定理又如下变式:如果p是一个素数,且整数a与p互素,那么a p−1 − 1 可以被p整除,用模运算表示如下
(p是素数,a是整数,a与p互素)、还有一种表述是:如果p是一个素数,a是一个整数且a不包含因数p,那么a p−1 −1 可以被p整除。费马小定理是费马素性测试的基础。费马在给出此定理的时候未给出证明,第一个证明其的人是Gottfried Leibniz。
费马素性测试是判断一个数是否为素数的一个基于概率的测试。事实上,费马小定理的逆否定理成立,而费马小定理的逆定理是不成立的,而费马素性测试就是基于费马小定理的“逆定理”的。大概的算法描述是,当p为奇数时(偶数特判一下就行啦,不就一个2嘛)让a在1-p之间(包括1和p)选取随机值,如果等式不成立,那么p肯定不是素数,如果成立,那么p就有较大可能是素数,我们称他为伪素数。当然,费马素性测试是有极大缺陷的,因而基本上平时没有多大用武之地。一个缺陷就是Carmichael数的存在,Carmichael数是指如果一个数n可以通过所有‘a’值的费马素性测试却并非为素数,那么就叫n为Carmichael数。这样的数随着n的增大而越来越少的,这些数中,最小的一个是561.
费马测试的具体实现是,对于N,从素数表中取出任意的素数对其进行费马测试,如果取了很多个素数,N仍未测试失败,那么则认为N是素数。当然,测试次数越多越准确,但一般来讲50次就足够了。另外,预先用“小学生”的算法构造一个包括500个素数的数组,先对Q进行整除测试,将会大大提高通过率。
代码实现如下:
i
- int Montgomery(int n,int p,int m)
- {
- //快速计算(n^e)%m的值,即逐次平方法
- intk=1;
- n%=m;
- while(p!=1)
- {
- if(0!=(p&1))
- k=(k*n)%m;
- n=(n*n)%m;
- p>>=1;
- }
- return(n*k)%m;
- }
- void prime(int n)
- {
- np = 0;
- for (int i = 2; i <= n; i++)
- {
- if (!isprime[i]) p[np++] = i;
- for (int j = 0; j < np && p[j]*i<= n; j++)
- {
- isprime[p[j]*i] = 1;
- if( i % p[j] == 0) break;
- }
- }
- }
- bool IsPrime3(int n)
- {
- if ( n < 2 ) // 小于2的数即不是合数也不是素数
- {
- return false;
- }
- for (int i=0; i<np; ++i)
- {
- // 按照素数表中的数对当前素数进行判断
- if (1!=Montgomery(p[i],n-1,n))//蒙格马利算法
- {
- return false;
- }
- }
- return true;
- }
int Montgomery(int n,int p,int m)
{//快速计算(n^e)%m的值,即逐次平方法intk=1;n%=m;while(p!=1){if(0!=(p&1))k=(k*n)%m;n=(n*n)%m;p>>=1;}return(n*k)%m;
}
void prime(int n)
{np = 0;for (int i = 2; i <= n; i++){if (!isprime[i]) p[np++] = i;for (int j = 0; j < np && p[j]*i<= n; j++){isprime[p[j]*i] = 1;if( i % p[j] == 0) break;}}
}
bool IsPrime3(int n)
{if ( n < 2 ) // 小于2的数即不是合数也不是素数{return false;}for (int i=0; i<np; ++i){// 按照素数表中的数对当前素数进行判断if (1!=Montgomery(p[i],n-1,n))//蒙格马利算法{return false;}}return true;
}
七、 米勒-拉宾素性测试
拉宾米勒测试是一个不确定的算法,只能从概率意义上判定一个数可能是素数,但并不能确保。但是也是目前公认最高效的素性测试之一。
算法流程如下:1.选择T个随机数A,并且有A<N成立。2.找到R和M,使得N=2*R*M+1成立。快速得到R和M的方式:N用二进制数B来表示,令C=B-1。因为N为奇数(素数都是奇数),所以C的最低位为0,从C的最低位的0开始向高位统计,一直到遇到第一个1。这时0的个数即为R,M为B右移R位的值。3.如果A^M%N=1,则通过A对于N的测试,然后进行下一个A的测试4.如果A^M%N!=1,那么令i由0迭代至R,进行下面的测试5.如果A^((2^i)*M)%N=N-1则通过A对于N的测试,否则进行下一个i的测试6.如果i=r,且尚未通过测试,则此A对于N的测试失败,说明N为合数。7.进行下一个A对N的测试,直到测试完指定个数的A
通过验证得知,当T为素数,并且A是平均分布的随机数,那么测试有效率为1 / ( 4 ^ T )。如果T > 8那么测试失误的机率就会小于10^(-5),这对于一般的应用是足够了。如果需要求的素数极大,或着要求更高的保障度,可以适当调高T的值。
代码实现如下:
- long long Pow_mod(long long bs,long longpower,long long diver)
- {
- if(power==0) return(1);
- else if(power==1) return(bs);
- else if ((power&1)==0)return(Pow_mod(bs*bs%diver,(power>>1),diver));
- elsereturn(Pow_mod(bs*bs%diver,power/2,diver)*bs%diver);
- }
- bool M_R(long long base,long long num)
- {
- d=num-1;
- while((d&1)==0)
- {
- d=(d>>1);
- }
- if((Pow_mod(base,d,num)==1)||(Pow_mod(base,d,num)==num-1))return true;
- else
- {
- t=(num-1)/2;
- while(d!=t)
- {
- d=(d<<1);
- if(Pow_mod(base,d,num)==num-1) return true;
- }
- return false;
- }
- }
long long Pow_mod(long long bs,long longpower,long long diver)
{if(power==0) return(1);else if(power==1) return(bs);else if ((power&1)==0)return(Pow_mod(bs*bs%diver,(power>>1),diver));elsereturn(Pow_mod(bs*bs%diver,power/2,diver)*bs%diver);
}
bool M_R(long long base,long long num)
{d=num-1;while((d&1)==0){d=(d>>1);}if((Pow_mod(base,d,num)==1)||(Pow_mod(base,d,num)==num-1))return true;else{t=(num-1)/2;while(d!=t){d=(d<<1);if(Pow_mod(base,d,num)==num-1) return true;}return false;}
}
由于能用逐次平方法在O(logn)的时间内算出a^bmod c.米勒-拉宾的算法时间主要是花在这里了,所有米勒-拉宾算法的时间复杂度是O(logn)的。
对于朴素判断优化的O(√n)要快了好多。
八、 总结与期望
通过以上7种判断素数方法的深入了解和代码实现,可以发现素数确实是数论中相当重要的一个组成元件。其涉及的方面相当广泛。通过以上几种方法的分析,我们能更清晰更具体的看到素数判断在不同的需求下,会有不同的算法选择。高效的筛法却不能逃避内存的限制,而米勒-拉宾测试是一种不确定的算法,有不确定性,这些都是高效算法所需要付出的代价。深入了解这些算法思想,能让我们在面对更多更难的问题时,能够冷静思考,从其定义和性质来分析,进一步分解问题,从而达到高效的解决。
素数判断的几种方法代码实现及其复杂度分析相关推荐
- 素数(质数)判断的五种方法
素数判断的五种方法 素数判断是我们写程序过程中经常遇见的一个问题,于是今天我简单地整理一下常用的素数判断的方法. 素数的介绍 素数定义 质数(prime number)又称素数,有无限个.一个大于1的 ...
- 面试系列第2篇:回文字符串判断的3种方法!
作者 | 磊哥 来源 | Java面试真题解析(ID:aimianshi666) 转载请联系授权(微信ID:GG_Stone) 回文字符串判断是面试和笔试中常见的面试题之一,同时也是 LeetCode ...
- python if多条件并列判断的三种方法
python if多条件并列判断的三种方法 如果使用python的if进行多个条件表达式的判断呢?下面介绍三种方法: 使用and或or来连接多个条件表达式,比如条件1 and 条件2 and条件3等等 ...
- 回文字符串判断的3种方法
回文字符串判断是面试和笔试中常见的面试题之一,同时也是 LeetCode 中一道经典的面试题,那么本文我们就来看一下什么是回文字符串?以及如何实现回文字符串的判断. 回文字符串定义 回文字符串是一个正 ...
- 判断元素16种方法expected_conditions
经常有小伙伴问,如何判断一个元素是否存在,如何判断alert弹窗出来了,如何判断动态的元素等等一系列的判断,在selenium的expected_conditions模块收集了一系列的场景判断方法,这 ...
- ML之xgboost:绘制xgboost的二叉树graphviz的两种方法代码实现
ML之xgboost:绘制xgboost的二叉树graphviz的两种方法代码实现 目录 绘制xgboost的二叉树graphviz T1.采用to_graphviz法绘制树图 T2.采用plot_t ...
- python如何删除代码_Python列表删除的三种方法代码分享
1.使用del语句删除元素 >>> i1 = ["a",'b','c','d'] >>> del i1[0] >>> prin ...
- PHP网站开启gzip压缩,php中开启gzip压缩的2种方法代码
Gzip网页压缩可以大幅度提升网站访问速度,对于网站在国外的站来说,这是必不可少的一步,提升网页打开速度非常明显,现在我们就系统的来认识一下这个Gzip的庐山真面目. 一.何为GZIP GZIP概念 ...
- ML:模型训练/模型评估中常用的两种方法代码实现(留一法一次性切分训练和K折交叉验证训练)
ML:模型训练/模型评估中常用的两种方法代码实现(留一法一次性切分训练和K折交叉验证训练) 目录 模型训练评估中常用的两种方法代码实现 T1.留一法一次性切分训练 T2.K折交叉验证训 模型训练评估中 ...
最新文章
- python培训机构推荐-Python培训班哪家好?老男孩Python入门学习
- Android 动画(三)--属性动画
- 大型网站技术架构02 网站的高性能架构、网站的可用性架构
- CF960G-Bandit Blues【第一类斯特林数,分治,NTT】
- 对于局部变量_对于SQL常用查询优化方法的整理
- 配置了坐标还是找不到serv_你那么努力,为何还是找不到工作?从优势发展观来看个人职业发展...
- 字体方向 道路标注_自动驾驶环境感知的“见闻色”——3D点云标注
- 杂记 - 进化成更好的人
- 5G iPhone消息刺激?苹果股价3连涨市值已超过1.2万亿美元
- 中国双接口芯片卡市场趋势报告、技术动态创新及市场预测
- python asyncio tcp server_关于 asyncio 创建多个 tcp 连接,线程数不准确的问题
- 莫烦python视频顺序_莫烦Python视频笔记
- 情侣博客源码php,wordpress如何搭建简单的情侣博客
- 福昕阅读器中删除单个,多个注释,隐藏所有注释。
- 计算机管理员被停用,命令提示符已被系统管理员停用,详细教您命令提示符已被系统管理员停用怎么办...
- 【云计算的1024种玩法】使用阿里云+微擎打造微信公众号管理系统
- golang学习(二)—— 变量
- 苹果录屏功能没有声音_手机录屏没有声音如何处理?可以从这三个方面入手看看...
- Lenovo 使用BoMC工具制作微码升级U盘刷新System x
- 给父母的礼物!一键让Android变身老人机
热门文章
- 论文笔记:nnU-Net: Self-adapting Frameworkfor U-Net-Based Medical Image Segmentation
- Android获取mp3音频文件播放总时长
- JS判断是否是JSON数据
- 邮件标题怎么写才好?
- 猿创征文|我Java开发那些年陪我成长的工具清单
- 热轧服务器系统管理,二级系统概述.pdf
- keyboard_键盘上的符号以及对应的英文名称
- python自动扫雷_Python自动扫雷实现方法
- 论文研读—基于 AUTOSAR 的汽车故障诊断系统的设计与实现
- 号称“高薪、转型、改变命运” ,纽约时报曝光煤矿工地上的编程“速成班”