1. 程式人生 > >埃氏篩法和尤拉篩法的區別

埃氏篩法和尤拉篩法的區別

Eratosthenes篩法(Sieve of Eratosthenes) 由於思想非常簡單,故只給出實現。

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;
    }
}

時間複雜度 。(窩不會證明)

Euler篩法(Sieve of Euler)

尤拉篩是一種線性演算法,並且同時可以計算出每個數的 。

做法

回顧經典的Eratosthenes篩法,它可能對同一個質數篩去多次。那麼如果用某種方法使得每個合數只被篩去一次就變成是線性的了。 不妨規定每個合數只用其最小的一個質因數去篩,這便是尤拉篩了。 不妨先看程式碼:

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的含義。 時間複雜度 。

簡單證明

這個看似很簡單,其實還是要注意一下細節的。搞清了證明其他的問題也就清楚了。 證明分兩部分。首先證每個合數都會被篩到(正確性),其次證每個合數只會被篩到一次(複雜度)。

每個合數都會被篩到

設有一合數 (為質數) 則一定會在 時被篩去(此時 ),因為對於小於 的質數,一定不會被 整除

每個合數都只會被篩到一次

與上面一樣,還是設有一合數 ( 為質數) 倘若存在一個質因子 也篩去了,那麼此時 。 ,此時在內層迴圈中已經早早地break掉了,因為 。 ,此時還沒加進質數表QwQ(順便一提:這種情況只有可能在 時發生) 難以理解的地方

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

你可以去試試……實踐出真知。 放前面的話,所有的“某個質因子的次數不為1”的合數便會被當成質數。至於為什麼,請看證明。

j < totPrimes為何不加?

實踐才是檢驗真理的唯一標準。 當 為質數時,內層迴圈會在最後一個質數(也就是 自己)終止。當 為合數時,內層迴圈會在它的第一個質因數終止。 當然加了也沒有問題。

順便把 算出來?

其實這是極簡單的。 主要基於以下事實:(很容易通過定義推出來,不妨自己試試)

1.n為質數時,phi(n) = n - 1

2.p為質數且p整除n時,phi(np) = p phi(n)

3.p為質數且p不整除n時,phi(n*p) =(p - 1) * phi(n)

有沒有發現簡直就是為Euler篩法量身定做的! 程式碼:

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;
            }
        }
    }
}

速度比較 你可能會覺得 微不足道,那麼你錯了。 實測結果:(精確到微秒,編譯時不開啟優化開關)

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)

差距還是蠻大的呢。