素数之恋——厄拉多塞筛法的时间复杂度的分析与理解
文章结构:算法描述(算法部分)与时间复杂度计算(数学部分)
一.算法部分
问题引入:设计一个算法,求出区间[1,N]中素数的个数,要求时间复杂度尽可能地小。
问题分析:
1.一般的枚举(暴力)算法的思想就是,设置双层循环,对于1<n<N,对单次的n值进行判断,如为素数则给计数器count+1,否则进行n+1的下一次判断…
此种枚举算法的时间复杂度为
O
(
N
2
)
O(N^2)
O(N2)
2.考虑到整数因子的成对特性,即x是y的因数,y/x也是x的因数。在进行单次n的素性判断时,而如果我们每次选择校验两者中的较小数,则不难发现较小数一定落在[2,sqrt(n)]
进行n%i时,可将i的上限改为
n
\sqrt{n} \quad
n
即内层循环次数可以降到根号n,从而使整个算法的时间复杂度降到
O
(
N
3
/
2
)
O(N^ {3/2})
O(N3/2)
优化后的代码如下:
bool isPrime(int x) {
for (int i = 2; i * i <= x; ++i) {
if (x % i == 0) {
return false;
}
}
return true;
}
int countPrimes(int n) {
int ans = 0;
for (int i = 2; i < n; ++i) {
ans += isPrime(i);
}
return ans;
}
//代码来源:LeetCode
3.事实上,至此,枚举已经难以继续优化。如果考虑到数与数的关联性,我们考虑这样一个事实:即如果 P是质数,那么比P大的2P,3P,4P…一定不是质数,因此我们根据这个思路来优化算法。
我们设 isPrime[P]表示数 P是不是质数,如果是质数则isPrime[P]=1,否则为 0。首先我们默认数组中的每个元素值都为1,即质数;从小到大遍历每个数,如果这个数为质数,则将其所有的倍数都标记为合数(除了该质数本身),即 isPrime[P]=0,通过每次遍历修改isPrime[i]的值,若值为1,给计数器加1。这样在程序结束后,就可以得到质数的个数。
这便是筛选法的思路。
代码如下:
int countPrimes(int n) {
if (n < 2) {
return 0;
}
int isPrime[n];
memset(isPrime, 1, sizeof(isPrime));//将数组所有元素初始化为0;
int ans = 0;
for (int i = 2; i < n; ++i) {
if (isPrime[i]) {
ans += 1;
if ((long long)i * i < n) {
//算法优化,对于一个质数p,如果从2p开始标记其实是冗余的,应该直接从p*p开始标记,因为2p,3p… 这些数一定在之前就被其他数的倍数标记过了,例如2的所有倍数,3的所有倍数等。
for (int j = i * i; j < n; j += i) {
isPrime[j] = 0;
}
}
}
}
return ans;
//代码来源:LeetCode
——————————————————————
二.数学部分
首先,给出结论,埃氏筛的时间复杂度为
O
(
n
ln
ln
n
)
O(n\ln \ln n)
O(nlnlnn)
下面计算算法的时间复杂度。
1.最朴素的筛法
for(int i=2;i<=n;i++)
for(int j=2;j*i<=n;j++)
prime[i*j]=0;
外层循环为n,当外层循环确定一个数时,设为i,则内层的循环次数 K 为:
⌊
n
i
⌋
−
1
<
K
<
n
i
\lfloor {\frac ni}\rfloor-1<K<\frac ni
⌊in⌋−1<K<in
则循环总数为
n
2
+
n
3
+
n
4
+
⋯
+
n
n
\frac n2+\frac n3+\frac n4+\cdots+\frac nn
2n+3n+4n+⋯+nn即
n
×
(
1
2
+
1
3
+
1
4
+
⋯
+
1
n
)
n\times(\frac 12+\frac 13+\frac 14+\cdots+\frac 1n)
n×(21+31+41+⋯+n1)
易知,幂级数
ln
(
1
+
t
)
=
t
−
t
2
2
+
t
3
3
−
t
4
4
+
t
5
5
−
⋯
\ln(1+t)=t-\frac{t^2}{2}+\frac{t^3}{3}-\frac{t^4}{4}+\frac{t^5}{5}-\cdots
ln(1+t)=t−2t2+3t3−4t4+5t5−⋯
令
t
=
1
x
t=\frac 1x
t=x1则有
ln
(
x
+
1
x
)
=
1
x
−
1
2
x
2
+
1
3
x
3
−
1
4
x
4
+
⋯
\ln(\frac {x+1}{x})=\frac 1x-\frac{1}{2x^2}+\frac{1}{3x^3}-\frac{1}{4x^4}+\cdots
ln(xx+1)=x1−2x21+3x31−4x41+⋯
将
1
x
\frac 1x
x1和
ln
(
x
+
1
x
)
\ln(\frac {x+1}{x})
ln(xx+1)移到等式左边和右边,再分别将
x
=
1
,
2
,
3
,
…
,
n
x=1,2,3,\ldots,n
x=1,2,3,…,n代入上式,得
1
1
=
ln
2
+
1
2
×
1
−
1
3
×
1
+
1
4
×
1
−
⋯
\frac 11=\ln 2+\frac {1}{2\times1}-\frac {1}{3\times1}+\frac {1}{4\times1}-\cdots
11=ln2+2×11−3×11+4×11−⋯
.
.
.
.
.
.
.
.
.
1
n
=
ln
n
+
1
n
+
1
2
n
2
−
1
3
n
3
+
1
4
n
4
−
⋯
\frac 1n=\ln\frac {n+1}{n}+\frac {1}{2n^2}-\frac {1}{3n^3}+\frac {1}{4n^4}-\cdots
n1=lnnn+1+2n21−3n31+4n41−⋯
将上式左右两边的对应项分别相加,我们得到:
∑
i
=
1
n
1
i
=
ln
(
∏
i
=
2
n
i
+
1
i
)
+
1
2
(
1
+
1
4
+
1
9
+
⋯
+
1
n
2
)
\sum_{i=1}^{n} {\frac 1i}=\ln({\prod_{i=2}^{n} {\frac {i+1}{i}}})+\frac12(1+\frac14+\frac19+\cdots+\frac{1}{n^2})
i=1∑ni1=ln(i=2∏nii+1)+21(1+41+91+⋯+n21)
−
1
3
(
1
+
1
8
+
1
27
+
⋯
+
1
n
3
)
-\frac13(1+\frac18+\frac{1}{27}+\cdots+\frac{1}{n^3})
−31(1+81+271+⋯+n31)
+
1
4
(
1
+
1
16
+
1
81
+
⋯
+
1
n
4
)
−
⋯
+\frac14(1+\frac{1}{16}+\frac{1}{81}+\cdots+\frac{1}{n^4})-\cdots
+41(1+161+811+⋯+n41)−⋯
(Hey!(>_<)冲啊,胜利就在眼前!)
即:当n充分大趋于无穷时:
左
边
=
ln
(
n
+
1
)
+
1
2
ζ
(
2
)
−
1
3
ζ
(
3
)
+
1
4
ζ
(
4
)
−
⋯
左边=\ln(n+1)+\frac12\zeta(2)-\frac13\zeta(3)+\frac14\zeta(4)-\cdots
左边=ln(n+1)+21ζ(2)−31ζ(3)+41ζ(4)−⋯
(感兴趣的小伙伴可以自行百度ζ(s)函数,这里不再赘述)
根据黎曼ζ函数性质可知,s>=2时,ζ(s)都是收敛于某一个常数的,如:
ζ
(
2
)
=
π
2
6
\zeta(2)=\frac{\pi^2}{6}
ζ(2)=6π2
(
仅
知
ζ
(
3
)
是
一
个
无
理
数
)
(仅知ζ(3)是一个无理数)
(仅知ζ(3)是一个无理数)
后项是调和级数与自然对数的差值的极限
lim
n
→
∞
[
(
∑
k
=
1
n
1
k
)
−
ln
n
]
\lim_{n \to \infty} \left[\left(\sum_{k=1}^{n} {\frac 1k}\right)-\ln n\right]
n→∞lim[(k=1∑nk1)−lnn]欧拉算出了后项的和,称为欧拉常数γ,约等于0.57721。
(没想到吧,又是我Euler)
γ
=
∑
m
=
2
+
∞
ζ
(
m
)
(
−
1
)
m
m
\gamma\quad=\quad\sum_{m=2}^{+\infty} {\frac {\zeta(m)(-1)^m}{m}}
γ=m=2∑+∞mζ(m)(−1)m
咳咳!常数项的减少并不影响级数的敛散性,记后项的和为常数gamma.
左
边
=
ln
(
n
+
1
)
+
γ
左边=\ln(n+1)+\gamma
左边=ln(n+1)+γ则在朴素的筛选方法下,此算法的时间复杂度为:
O
(
n
ln
n
)
O(n\ln n)
O(nlnn)
2.只针对素数进行筛选的代码:
for(int i=2;i<=n;i++)
if(prime[i])
for(int j=2;j*i<=n;j++)
prime[i*j]=0;
对于经典的埃氏筛而言,对出现的所有的整数都进行向后筛选,在对朴素的筛法进行优化后,只会对质数向后筛选。
故在这种情况下,时间复杂度一定小于:
n
ln
n
n\ln n
nlnn
那具体的复杂度到底是多少呢?
不难于理解,这道题内层循环次数本质上是求1到N之间所有素数对应的N/P(i)(i表示第i个素数)之和,至于这个和的具体值是多少,业已超出我的知识范围(有些地方严格意义上已经涉及数论的内容,实在很难理解),历史上也有很多数学家付出心血研究这个问题。
感兴趣的同学可以参考一些数论的书籍,或移步至这位兄弟的文章~( ̄▽ ̄~)~:
另外,放一张来自知乎用户:周星宇
针对这个问题的回答
参考资料:《什么是数学:对思想和方法的基本研究 R.科朗&H.罗宾》