一、试除法判断素数
素数的定义是只能被常数1或自己整除,不能被其他整数整除的正整数。即不能被[2, n-1]区间内的整数整除的都是素数(2,3除外),所以可以从2开始试除,直到n-1,如果都不能整除,那就是素数,否则就是合数。当然这个可以优化,只需要试除到 n \sqrt{n} n 就可以了,一个数如果被小于 n \sqrt{n} n的数整除,必然被另一个大于 n \sqrt{n} n的数整数。以下是试除法的代码。
bool isPrime(int n){
if(n <= 1) return false;
for(int i = 2; i <= n/i; i++){
if(n % i == 0) return false;
}
return true;
}
二、埃式筛法
如果是求一个范围内的所有素数,用试除法一个一个去判断就太慢了,埃氏筛法可以很好解决这个问题,它的时间复杂度为 O ( n l o g ( n ) ) O( nlog(n) ) O(nlog(n)),可以快速找出[2, n-1]中所有素数,它的做法是输出最小素数,并筛掉它的倍数,例:对于数列{2,3,4,5,6,7,8,9,10,11,12}进行如下操作:
- 取出最小素数2,并筛去2的倍数,剩下{3,5,7,9,11}。
- 取出最小素数3,并筛去3的倍数,剩下{5,7,11}。
- 取出最小素数5,并筛去5的倍数,剩下{7,11}。
- 取出最小素数7,并筛去7的倍数,剩下{11}。
- 取出最小素数11,并筛去11的倍数,数列为空,结束操作。
代码如下:
const int maxn = 1e7;
int Prime[maxn];
bool visit[maxn];
int EratSieve(int n){
int len = 0;
for(int i = 0; i <= n; i++){
visit[i] = false;
}
for(int i = 2; i <= n/i; i++){
if(!visit[i]){
for(int j = i*i; j <= n; j += i){
visit[j] = true;
}
}
}
for(int i = 2; i <= n; i++){
if(!visit[i]) prime[len++] = i;
}
return len;
}
三、欧拉筛法
上面讲的埃氏筛法虽然可以快速筛去区间[2, n] 内的素数,但是有些数也重复筛了,如12被 2 和 3 筛了 2 次,下面介绍的欧拉筛法很好地解决了这个问题,它的时间复杂度仅为 O ( n ) O(n) O(n)。代码如下:
const int maxn = 1e7;
int Prime[maxn];
bool visit[maxn];
int Euler(int n){
int len = 0;
for(int i = 0; i <= n; i++){
visit[i] = false;
}
for(int i = 2;i <= n; i++){
if(!visit[i]){
prime[len++] = i;
}
for(int j = 0; j < len; j++){
if(i*prime[j] > n) break;
visit[i*prime[j]] = true;
if(i%prime[j] == 0) break;
}
}
return len;
}
第一个for循环的i即做筛选的数,也做筛选的倍数。做筛选的数时,从2开始筛选,直到n,标记为false的即为素数;做筛选的倍数时,每轮筛选都遍历素数列表,把素数列表的i倍置为true,但是如果倍数为合数,可能会中途退出,以n=12为例,有以下流程:
- 2为false,是素数,加入素数队列,同时筛掉2的2倍,即筛掉4。
- 3为false,是素数,加入素数列表,同时筛掉2,3的3倍,即筛掉6,9。
- 4为true,不是素数,筛掉2的4倍,4能被2整除,退出循环,即筛掉8。
- 5为false,是素数,加入素数列表,同时筛掉2,3,5的5倍,但15超过12,即筛掉10。
- 6为true,不是素数,筛掉2,3,5的6倍,但18超过12即筛掉12。
- 7为false,是素数,加入素数队列,但素数列表的7倍都超过12,不筛选。
- 8为true,不是素数,素数列表的8倍都超过8,不筛选。
以此类推…
从上面可以看出来,用欧拉筛法不会重复筛同一个数,当数据很大时,可以大大降低时间复杂度。
有问题欢迎私信我!