素数算法吐血总结
2017-12-17 22:07
141 查看
扯淡
相信很多童鞋在OJ都遇到过关于素数的问题,对于素数相关问题,一般都会给出答案,但是相信并不是所有人能给出最优解,包括本菜鸟,故吐血整理一波,以供学习回顾。素数定义
既然都说素数、质数什么的,那么什么是素数呢?质数(Prime number),又称素数,指在大于1的自然数中,除了1和该数自身外,无法被其他自然数整除的数(也可定义为只有1与该数本身两个正因数的数)。大于1的自然数若不是素数,则称之为合数。
问题
对于素数相关问题,一般会有三种问题。判断某个数是否为素数。
打印出小于自然数
N的素数。
对于给定的自然数
N,从小到大依次打印出
N个素数。
素数判断
试除法
如果要判断某个自然数X是否为素数,,这就是试除法
初级版
假设要判断自然数X是否为素数,最简单的想法就是判断
X是否能够整除
1~
X之间的某个数。
中级版
如果自然数X存在质因数,那么必定会小于等于
X/2, 所以稍微动脑的人会将返回缩小到
1~
X/2, 减小一半的工作量
高级版
对于一个自然数,如果存在可整除的数,即X = a * b, 那么因数中必定会有一个因数小于X‾‾√X, 而另一个因数大于X‾‾√X。 故很多更聪明的程序猿会将返回缩小到
1~ X‾‾√X
上面的算法仅仅是判断某个数是否为质数,可是要得出素数的序列,仅仅通过遍历自然数然后进行素数判断可是最傻的办法,那么有什么简单高效的算法呢?
筛法
质数算法中的重头戏就是要说的本算法。埃拉托斯特尼筛法
基本思想素数的倍数一定不是素数。
做法
首先申请空间为
N+1的数组用来保存自然数
1 ~ N是否为质数,起初将所有自然数全部置为
0, (
0表示质数,
1表示合数),从第一个素数
2开始,将
2的倍数全部置为
1, 然后找到下一个素数
3,将
3的倍数全部置为
1, 直到找到最后一个小于
N的质数。此处引用维基百科的示例图。
代码实现
void make_prime() { memset(prime, 1, sizeof(prime)); prime[0] = false; prime[1] = false; int N = 31700; for (int i = 2; i < N; i++) if (prime[i]) { primes[++cnt ] = i; for (int k = i + i; k < N; k += i) prime[k] = false; } return; }
时间复杂度为O(nloglogn), 空间复杂度为O(n)
虽然已经很高效,但是仍然存在缺点,对于能够整除不同素数的自然数,筛了多次。例如
30, 在素数
2的时候,
k = 2 * 15筛过一次,素数为
3的时候,
k = 3 * 10又筛过一次。故出现了下面的改进算法。
欧拉筛法
const long N = 200000; long prime = {0}, num_prime = 0; int isNotPrime = {1, 1}; int main() { for (long i = 2 ; i < N ; i ++) { if (! isNotPrime[i]) prime[num_prime ++] = i; //关键处1 for (long j = 0 ; j < num_prime && i * prime[j] < N ; j ++) { isNotPrime[i * prime[j]] = 1; if ( !(i % prime[j] ) ) //关键处2 break; } } return 0; }
理解
首先,先明确一个条件,任何合数都能表示成一系列素数的积。 不管 i 是否是素数,都会执行到“关键处1”。
如果 i 都是是素数的话,那简单,一个大的素数 i 乘以不大于 i 的素数,这样筛除的数跟之前的是不会重复的。筛出的数都是 N=p1*p2的形式, p1,p2之间不相等
如果 i 是合数,此时 i 可以表示成递增素数相乘 i=p1p2…*pn, pi都是素数(2<=i<=n), pi<=pj ( i<=j ) p1是最小的系数。
根据“关键处2”的定义,当p1==prime[j] 的时候,筛除就终止了,也就是说,只能筛出不大于p1的质数*i。
我们可以直观地举个例子:i=2*3*5 此时能筛除 2*i ,不能筛除 3*i, 如果能筛除3*i 的话,当 i’ 等于 i’=3*3*5 时,筛除2*i’ 就和前面重复了。
引用
埃拉托斯特尼筛法求质数算法的 N 种境界[1] - 试除法和初级筛法
数论——筛法求素数
相关文章推荐