从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 ; }每次遍历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 ; }每次遍历到合数的时候也不需要判断了 比如: 从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 ; }观察: 从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 24 30 36 都是合数(此处改变,因为2是6的最小素因子) 从7(素数)开始:14 21 28 35 42 49都是合数 从8 开始:16 24 32 40 48 56 64 都是合数(此处改变,因为2是8的最小素因子) 从9 开始:18 27 36 45 54 63 72 81 都是合数(此处改变,因为3是9的最小素因子) 从10 开始:20 30 40 50 60 70 80 90 100 都是合数(此处改变,因为2是10的最小素因子) … … 神奇!!!!! 我们在写清楚点:
取最小素数做乘积: 从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 ; }