1暴力筛选素数
从2开始:4 6 8 10 12 14 16 18 20 …都是合数
从3开始:6 9 12 15 18 21 24 …都是合数
从4开始:8 12 16 20 24 28 …都是合数
…
…
从2开始依次向后开始遍历(i),每次遍历过程中,都乘以2,乘以3,乘以4标记(这里写成每次都加上i,也就是相乘的意思)
#include <stdio.h>
#define MAX_N 100
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
for(int j = 2 ; j <= MAX_N; j += i){
prime[j] = 1;
}
}
return ;
}
2少重复一点
每次遍历i的时候,比i小的数可以不用判断了,
比如:
从2开始:4 6 8 10 12 14 16 18 20 …都是合数
从3开始:6 9 12 15 18 21 24 …都是合数
从4开始:8 12 16 20 24 28 …都是合数
…
…
3不用从32开始
4不用从42,4*3开始
因为前面肯定访问过了。
修改j = i * i
改进之后:
从2开始:4 6 8 10 12 14 16 18 20 …都是合数
从3开始:9 12 15 18 21 24 …都是合数
从4开始:16 20 24 28 …都是合数
…
…
#include <stdio.h>
#define MAX_N 100
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
for(int j = i * i ; j <= MAX_N; j += i){
prime[j] = 1;
}
}
return ;
}
3再少重复一点
每次遍历到合数的时候也不需要判断了
比如:
从2开始:4 6 8 10 12 14 16 18 20 …都是合数
从3开始:9 12 15 18 21 24 …都是合数
从4开始:16 20 24 28 …都是合数
…
…
4的所有标记肯定都被比他小的一个数标记了,因为它是合数,肯定能被分解。
所以,我们只需要对每个素数i标记就行合数肯定会被之前标记过
改进:
增加一句if(prime[i]) continue;
从2开始:4 6 8 10 12 14 16 18 20 …都是合数
从3开始:9 12 15 18 21 24 …都是合数
从4开始:16 20 24 28 …都是合数
从5开始:25 30 35 40 45 50…都是合数
从6开始:36 42 48 54 60 …都是合数
从7开始:49 56 63 70 77 …都是合数
…
…
#include <stdio.h>
#define MAX_N 100
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
if(prime[i]) continue;
for(int j = i * i ; j <= MAX_N; j += i){
prime[j] = 1;
}
}
return ;
}
4一个都不允许重复
观察:
从2开始:4 6 8 10 12 14 16 18 20 22 24 26…都是合数
从3开始:9 12 15 18 21 24 27 30…都是合数
从5开始:25 30 35 40 45 50…都是合数
从7开始:49 56 63 70 77 …都是合数
…
…
还是存在一定数字是被重复标记的,比如24、30等等
能不能再优化一下呢?
思考…
…
…
24被第一个碰到的素数2标记之后,就不应该被3这个素数标记了
30被第一个碰到的素数2标记之后,就不应该被3这个素数标记了
30被第一个碰到的素数2标记之后,就不应该被5这个素数标记了
…
…
如果按照上面这种说法(狗屎理解气死人,大多数都是这么解释的)那就错了。
**24被第一个碰到的素数2标记之后,就不应该被3这个素数标记了
30被第一个碰到的素数2标记之后,就不应该被3这个素数标记了
30被第一个碰到的素数2标记之后,就不应该被5这个素数标记了
…
…
应该变为
24被第一个碰到的素数2标记之后,就不应该 只能被3这个素数标记
30被第一个碰到的素数2标记之后,就不应该 只能被3这个素数标记
30被第一个碰到的素数2标记之后,就不应该 只能被5这个素数标记
…
…
**
这么说是不是更蒙了?这也就是大多数人讲不清楚的地方,我们利用素数标记其他数时,会做许多多余的操作
2把不该它标记的24和30都标记了,以至于到了3标记的时候会重复,所以为了避免重复,我们强烈要求2不要多管闲事(而不是叫3不要去标记24),只标记前面几个数后面的数应该然后它们自己去标记。
这里我们需要回过头去看第1个知识点:
(i从2到MAX_N都枚举)
从2开始:4 6 8 10 12 14 16 18 20 22 24 26…都是合数
从3开始:6 9 12 15 18 21 24 27 30…都是合数
从4开始:8 12 16 20 24 28 …都是合数
从5开始:10 15 20 25 30 35 40 45 50…都是合数
从6开始:12 18 24 30 36 42 48 …都是合数
从7开始:14 21 28 35 42 49 56 63 70 77 …都是合数
…
…
这里我们还需要回过头去看第3个知识点:
从2开始:4 6 8 10 12 14 16 18 20 …都是合数
从3开始:9 12 15 18 21 24 …都是合数
从4开始:16 20 24 28 …都是合数
从5开始:25 30 35 40 45 50…都是合数
从6开始:36 42 48 54 60 …都是合数
从7开始:49 56 63 70 77 …都是合数
…
…
在上两个知识点上,我们从2到MAX_N都遍历,但是取数的时候,只取素数(这个素数目前已知的素数)做乘积:
没取素数做乘积:
从2(素数)开始:4 6 8 10 12 14 16 18 20 22 24 26… 都是合数
从3(素数)开始:6 9 12 15 18 21 24 27 30… 都是合数
从4开始:8 12 16 20 24 28 … 都是合数
从5(素数)开始:10 15 20 25 30 35 40 45 50… 都是合数
从6开始:12 18 24 30 36 42 48 … 都是合数
从7(素数)开始:14 21 28 35 42 49 56 63 70 77 …都是合数
…
…
取素数做乘积:
从2(素数)开始:4 都是合数
从3(素数)开始:6 9 都是合数
从4 开始:8 12 16都是合数
从5(素数)开始:10 15 20 25(20不可以因为4是合数4*5不行)都是合数
从6 开始:12 18 24 30 36都是合数
从7(素数)开始:14 21 28 35 42 49都是合数
从8 开始:16 24 32 40 48 56 64都是合数
从9 开始:18 27 36 45 54 63 72 81都是合数
从10 开始:20 30 40 50 60 70 80 90 100 都是合数
…
…
这样确实可以少重复很多次,但一定不是线性的O(n)时间复杂度,因为12、16、18、24、这样的数字还会重复(虽然重复率被我们降到很低)
我们能不能保证一次都不重复呢?可以!一次都不重复筛选叫线性筛。线性筛:每个合数必有一个最小素因子。可以自证,也可也看百科。
12、16、18、24为什么会重复?不就是因为它的最小素因子是2吗?然后又被3这个素因子给标记了一次导致重复。
所以我们把刚才取素数做乘积增加一个限制,取到最小素数(最小素因子)做乘积就停止,这样就可以
取最小素数做乘积:
从2(素数)开始:4 都是合数
从3(素数)开始:6 9 都是合数
从4 开始:8 12 16 都是合数(此处改变,因为2是4的最小素因子)
从5(素数)开始:10 15 20 25(20不可以因为4是合数4*5不行)都是合数
从6 开始:12 18 都是合数(此处改变,因为2是6的最小素因子)24 30 36
从7(素数)开始:14 21 28 35 42 49都是合数
从8 开始:16 24 都是合数(此处改变,因为2是8的最小素因子)32 40 48 56 64
从9 开始:18 27 36 45 都是合数(此处改变,因为3是9的最小素因子)54 63 72 81
从10 开始:20 30 都是合数(此处改变,因为2是10的最小素因子)40 50 60 70 80 90 100
…
…
神奇!!!!!
我们在写清楚点:
取最小素数做乘积:
从2(素数) 开始: 4
从3(素数) 开始: 6 9
从4 开始: 8
从5(素数) 开始: 10 15 25
从6 开始: 12
从7(素数) 开始: 14 21 35 49
从8 开始: 16
从9 开始: 18 27
从10 开始: 20
…
…
欧几里得真的令人钦佩,大神!
下面来看代码:
我们每次都需要用到前面已经找到的素数,所以必须把素数保存一下,可以另外开一个数组保存,也可以在原数组上使用,不妨碍向后寻找素数。
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
//如果prime[i]是0,是素数就往前搬,prime[0]是计数器
if(!prime[i]) prime[++prime[0]] = i;
.......
.......
.......
}
return ;
}
保存之后,我们只在素数里面筛选合数,当前i和每个素数相乘来标记
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
//如果prime[i]是0,是素数就往前搬,prime[0]是计数器
if(!prime[i]) prime[++prime[0]] = i;
for(int j = 1; j <= prime[0]; j++){
prime[prime[j] * i] = 1;
}
}
return ;
}
此时需要线性筛的话就要把在最小素因子那里停下来,不要向后筛选了.怎么知道是不是最小素因子?i % prime[j] == 0只要能被除尽就是了,直接break
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
//如果prime[i]是0,是素数就往前搬,prime[0]是计数器
if(!prime[i]) prime[++prime[0]] = i;
for(int j = 1; j <= prime[0]; j++){
prime[prime[j] * i] = 1;
if(i % prime[j] == 0) break;//第一个标记之后停掉,后面不用标记了,线性筛的重点
}
}
return ;
}
特殊情况,不能越界哟 if(prime[j] * i > MAX_N) break;
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
//如果prime[i]是0,是素数就往前搬,prime[0]是计数器
if(!prime[i]) prime[++prime[0]] = i;
for(int j = 1; j <= prime[0]; j++){
if(prime[j] * i > MAX_N) break;
prime[prime[j] * i] = 1;
if(i % prime[j] == 0) break;//第一个标记之后停掉,后面不用标记了,线性筛的重点
}
}
return ;
}
附录1:线性筛表格
取最小素数做乘积: | |
---|---|
从2(素数)开始: | 4 |
从3(素数)开始: | 6 9 |
从4 开始: | 8 |
从5(素数)开始: | 10 15 25 |
从6 开始: | 12 |
从7(素数)开始: | 14 21 35 49 |
从8 开始: | 16 |
从9 开始: | 18 27 |
从10 开始: | 20 |
… | … |
附录2:可运行代码
#include <stdio.h>
#define MAX_N 100
int prime[MAX_N + 5];
void init(){
for(int i = 2; i <= MAX_N; i++){
if(!prime[i]) prime[++prime[0]] = i;
for(int j = 1; j <= prime[0]; j++){
if(prime[j] * i > MAX_N) break;
prime[prime[j] * i] = 1;
if(i % prime[j] == 0) break;
}
}
return ;
}
int main(){
init();
for(int i = 1; i <= prime[0]; i++){
printf("%d\n", prime[i]);
}
return 0;
}