素数之恋

素数之恋——厄拉多塞筛法的时间复杂度的分析与理解

文章结构:算法描述(算法部分)与时间复杂度计算(数学部分)

一.算法部分

问题引入:设计一个算法,求出区间[1,N]中素数的个数,要求时间复杂度尽可能地小。
问题分析:

1.一般的枚举(暴力)算法的思想就是,设置双层循环,对于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-1in1<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)=t2t2+3t34t4+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)=x12x21+3x314x41+
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×113×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+2n213n31+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=1ni1=ln(i=2nii+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] nlim[(k=1nk1)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
素数之恋_第1张图片咳咳!常数项的减少并不影响级数的敛散性,记后项的和为常数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个素数)之和,至于这个和的具体值是多少,业已超出我的知识范围(有些地方严格意义上已经涉及数论的内容,实在很难理解),历史上也有很多数学家付出心血研究这个问题。
感兴趣的同学可以参考一些数论的书籍,或移步至这位兄弟的文章~( ̄▽ ̄~)~:

另外,放一张来自知乎用户:周星宇
针对这个问题的回答
素数之恋_第2张图片

参考资料:《什么是数学:对思想和方法的基本研究 R.科朗&H.罗宾》

你可能感兴趣的:(笔记,算法,数学)