素数筛法
更好的阅读体验
现在提出一个问题:求出1到n的所有素数?
暴力做法
暴力的思想判断某个数是否是一个质数
public static boolean isPrime(int a)
{
for(int i=2;i<a;i++)
{
if(a%i==0)
return false;
}
return true;
}
我们可以分析一下这个算法的时间复杂度
对于每一个质数来说需要遍历到它本身才能知道是否一个质数
时间复杂度为 o ( n 2 ) o(n^2) o(n2)
我们设法改进,发现只需要遍历到a的根号即可
public static boolean isPrime(int a)
{
for(int i=2;i<=Math.sqrt(a);i++)
{
if(a%i==0)
{
return false;
}
}
return true;
}
埃氏筛
在筛法过程中,我们发现判断时如果这个数的最小因数为 p p p,则每次遍历时需要从 2 2 2到 p p p
为了改进筛法,我们找到有可能为因子的数,这就是埃氏筛的思想
我们写出到
2
到
16
2到16
2到16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2~~~3~~~4~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
开筛!
找到第一个没被筛过的数2,筛掉2的倍数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~9~~~\cancel{10}~~~11~~~\cancel{12}~~~13~~~\cancel{14}~~~15~~~\cancel{16}
2 3 4
5 6
7 8
9 10
11 12
13 14
15 16
继续,发现是
3
3
3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~\cancel{10}~~~11~~~\cancel{12}~~~13~~~\cancel{14}~~~\cancel{15}~~~\cancel{16}
2 3 4
5 6
7 8
9
10
11 12
13 14
15
16
继续,发现是
5
5
5,
5
5
5比
16
\sqrt{16}
16 大,所以不用在编历了,至此素数被筛出来了
代码如下
int[] isPrime=new int[N];
public static void init(int n)
{
for(int i=2;i<=Math.sqrt(n);i++)
{
if(!isPrime[i])
{
for(int j=i*i;j<=n;j+=i)
{
isPrime[j]=true;
}
}
}
}
埃氏筛的时间复杂度是 o ( n l o g l o g n ) o(nloglog_n) o(nloglogn),已经是很不错的算法了,
但我们在埃氏筛的过程中,可以发现有的数据被重复筛到了,例如12同时被2和3筛到
为了改进这个问题,我们希望每个数只被筛一次,2和3都是12的因子,那么12被谁筛呢?
我们希望每个数被它的最小质因子筛掉,这就是线性筛的思想
线性筛
线性筛在埃氏筛的基础上还维护了一个质数表
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p
r
i
m
e
s
:
(
)
2~~~3~~~4~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:()
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16primes:()
遍历,找到第一个未被划掉的数
2
2
2,加入素数表中
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p
r
i
m
e
s
:
(
2
,
)
2~~~3~~~4~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,)
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16primes:(2,)
用
2
2
2乘以素数表中的数,然后划掉结果
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p
r
i
m
e
s
:
(
2
,
)
2~~~3~~~\cancel{4}~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,)
2 3 4
5 6 7 8 9 10 11 12 13 14 15 16primes:(2,)
下一个数是
3
,
3
3,3
3,3未被划掉,加入素数表中,划掉
6
,
9
6,9
6,9
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p
r
i
m
e
s
:
(
2
,
3
)
2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~8~~~\cancel{9}~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,3)
2 3 4
5 6
7 8 9
10 11 12 13 14 15 16primes:(2,3)
继续,下一个数是4,4被划掉不加入素数表,但是参与运算
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p
r
i
m
e
s
:
(
2
,
3
)
2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,3)
2 3 4
5 6
7 8
9
10 11 12 13 14 15 16primes:(2,3)
此处我们划掉了
8
8
8,但我们不划掉
12
12
12
我们发现如果划掉 12 12 12,则是由 4 , 3 4,3 4,3因子划掉的,而12的最小质因子是2,违背了由最小质因子划掉的原则。而 4 , 3 4,3 4,3之所以能够划掉12,是因为4中隐含了质因子2,因此我们可以得出结论,当某个数隐含了某个质因子时,这个数就具有代替这个质因子划掉某些数的功能,因此我们要停止操作
继续操作,找到下一个数5,5未被筛掉,加入到素数表,划去10,15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p
r
i
m
e
s
:
(
2
,
3
,
5
)
2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~\cancel{10}~~~11~~~12~~~13~~~14~~~\cancel{15}~~~16 \\primes:(2,3,5)
2 3 4
5 6
7 8
9
10
11 12 13 14 15
16primes:(2,3,5)
以此类推,得到最终结果
…
\dots
…
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p
r
i
m
e
s
:
(
2
,
3
,
5
,
7
,
11
,
13
)
2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~\cancel{10}~~~11~~~\cancel{12}~~~13~~~\cancel{14}~~~\cancel{15}~~~\cancel{16} \\primes:(2,3,5,7,11,13)
2 3 4
5 6
7 8
9
10
11 12
13 14
15
16
primes:(2,3,5,7,11,13)
代码如下
int[] Primes=new int[N];
boolean[] isPrime=new boolean[N];
int cnt=0;
public static void init(int n)
{
for(int i=2;i<=n;i++)
{
if(!isPrime[i])
{
Primes[cnt++]=i;
for(int j=0;j<cnt;j++)
{
if(j*Primes[j]>n)
break;
isPrime[i*Primes[j]]=true;
if(i%Prime[j]==0)
break;
}
}
}
}
线性筛的时间复杂度为 o ( n ) o(n) o(n)
线性筛法的证明
要证明线性筛法是正确的,我们可以证明对于每一个合数,它一定能够被筛到并且只被筛到一次
为了简化证明,这里我们给出算术基本定理(唯一基本定理)
设 p p p 是素数,则对于每一个正整数 a a a
有如下的唯一表示方法
a
=
p
1
α
1
p
2
α
2
…
p
s
α
s
a=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_s^{\alpha_s}
a=p1α1p2α2…psαs
不妨设
p
1
<
p
2
<
⋯
<
p
s
p_1<p_2<\dots<p_s
p1<p2<⋯<ps
则
p
1
p_1
p1为最小质因子
我们使用反证法证明
- 某一个合数不能被筛到
已知 p 1 p_1 p1一定会被加入到质数表中
当遍历到 p 1 α 1 − 1 p 2 α 2 … p s α s p_1^{\alpha_1-1}p_2^{\alpha_2}\dots p_s^{\alpha_s} p1α1−1p2α2…psαs时,由于该数的质因子最小为 p 1 p_1 p1,因此一定会到乘以 p 1 p_1 p1这一步,则合数一定会被筛掉
- 某一个合数被筛到两次
由1可知,合数一定被筛到一次,若合数被筛到
假设遍历到了某个数 i i i
则有
i
∗
p
k
=
a
(
p
k
为
素
数
表
中
的
某
个
素
数
)
i*p_k=a(p_k为素数表中的某个素数)
i∗pk=a(pk为素数表中的某个素数)
又跟据
a
=
p
1
α
1
p
2
α
2
…
p
s
α
s
a=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_s^{\alpha_s}
a=p1α1p2α2…psαs
则
p
k
为
p
1
…
p
s
p_k为p_1\dots p_s
pk为p1…ps中的某一个
则
i
i
i可以写成如下形式
i
=
a
p
k
=
p
1
α
1
p
2
α
2
…
p
k
α
k
−
1
…
p
s
α
s
i=\dfrac{a}{p_k}=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_k^{\alpha^k-1}\dots p_s^{\alpha_s}
i=pka=p1α1p2α2…pkαk−1…psαs
则可知
i
i
i中含有质因子
p
1
p_1
p1则
i
i
i在第一次乘以质数表中的
p
1
p_1
p1后结束,因此不存在第二次筛去
综上得证线性筛法的正确性