埃氏筛法和欧拉筛法的区别

转自:

点击打开链接


Eratosthenes筛法(Sieve of Eratosthenes)

由于思想非常简单,故只给出实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void eratosthenes_sieve(int n)
{
    totPrimes = 0;
    memset(flag, 0, sizeof(flag));

    int sqrtn = sqrt(n + 0.5);
    for (int i = 2; i <= sqrtn; i++) {
        if (!flag[i]) {
            primes[totPrimes++] = i;
            for (int j = i * i; j <= n; j += i) {
                flag[j] = true;
            }
        }
    }
    for (int i = sqrtn + 1; i <= n; i++) {
        if (!flag[i])
            primes[++totPrimes] = i;
    }
}

时间复杂度 O(nloglogn)O(nlog⁡log⁡n)。(窝不会证明)

Euler筛法(Sieve of Euler)

欧拉筛是一种线性算法,并且同时可以计算出每个数的 φφ

做法

回顾经典的Eratosthenes筛法,它可能对同一个质数筛去多次。那么如果用某种方法使得每个合数只被筛去一次就变成是线性的了。
不妨规定每个合数只用其最小的一个质因数去筛,这便是欧拉筛了。
不妨先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void euler_sieve(int n)
{
    totPrimes = 0;
    memset(flag, 0, sizeof(flag));

    for (int i = 2; i <= n; i++) {
        if (!flag[i])
            primes[totPrimes++] = i;
        for (int j = 0; i * primes[j] <= n; j++) {
            flag[i*primes[j]] = true;
            if (i % primes[j] == 0)
                break;
        }
    }
}

请仔细体会i % primes[j] == 0的含义。
时间复杂度 O(n)O(n)

简单证明

这个看似很简单,其实还是要注意一下细节的。搞清了证明其他的问题也就清楚了。
证明分两部分。首先证每个合数都会被筛到(正确性),其次证每个合数只会被筛到一次(复杂度)。

每个合数都会被筛到

设有一合数 n=pk11...pkmmn=p1k1...pmkmpipi为质数)
nn一定会在 i=pk111...pkmmi=p1k1−1...pmkm 时被筛去(此时 primes[j]=p1primes[j]=p1),因为对于小于 p1p1的质数,一定不会被 ii 整除

每个合数都只会被筛到一次

与上面一样,还是设有一合数 n=pk11...pkmmn=p1k1...pmkmpipi 为质数)
倘若存在一个质因子 px(x1)px(x≠1) 也筛去了nn,那么此时 i=pk11...pki1x...pkmmi=p1k1...pxki−1...pmkm

  • i>pxi>px,此时在内层循环中已经早早地break掉了,因为 p1ip1∣i
  • i<pxi,此时pxpx还没加进质数表QwQ(顺便一提:这种情况只有可能在 x=mx=m 时发生)

难以理解的地方

i % primes[j] == 0为何不放在前面?

你可以去试试……实践出真知。
放前面的话,所有的“某个质因子的次数不为1”的合数便会被当成质数。至于为什么,请看证明。

j < totPrimes为何不加?

实践才是检验真理的唯一标准。

  • 当 ii 为质数时,内层循环会在最后一个质数(也就是 ii 自己)终止。
  • 当 ii 为合数时,内层循环会在它的第一个质因数终止。

当然加了也没有问题。

顺便把 φφ 算出来?

其实这是极简单的。
主要基于以下事实:(很容易通过定义推出来,不妨自己试试)

1.n为质数时,phi(n) = n - 1

2.p为质数且p整除n时,phi(n*p) = p* phi(n) 

3.p为质数且p不整除n时,phi(n*p) =(p - 1) * phi(n)


有没有发现简直就是为Euler筛法量身定做的!
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void euler_sieve_with_phi(int n)
{
    totPrimes = 0;
    phi[1] = 1;
    memset(flag, 0, sizeof(flag));

    for (int i = 2; i <= n; i++) {
        if (!flag[i]) {
            primes[totPrimes++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; i * primes[j] <= n; j++) {
            flag[i*primes[j]] = true;
            if (i % primes[j])
                phi[i*primes[j]] = phi[i] * (primes[j] - 1);
            else {
                phi[i*primes[j]] = phi[i] * primes[j];
                break;
            }
        }
    }
}

速度比较

你可能会觉得 loglognlog⁡log⁡n 微不足道,那么你错了。
实测结果:(精确到微秒,编译时不打开优化开关)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
when n = 10000
    eratosthenes_sieve(1229):     0(us)
    euler_sieve(1229):            0(us)
when n = 100000
    eratosthenes_sieve(9592):     999(us)
    euler_sieve(9592):            0(us)
when n = 1000000
    eratosthenes_sieve(78498):    13004(us)
    euler_sieve(78498):           7004(us)
when n = 10000000
    eratosthenes_sieve(664579):   185130(us)
    euler_sieve(664579):          79067(us)
when n = 100000000
    eratosthenes_sieve(5761455):  2363692(us)
    euler_sieve(5761455):         842592(us)
when n = 1000000000
    eratosthenes_sieve(50847534): 25535159(us)
    euler_sieve(50847534):        8987385(us)

差距还是蛮大的呢。


你可能感兴趣的:(数论)