Sito Eratostenesa
Najszybszy znany algorytm do znajdowania wszystkich liczb pierwszych w zadanym przedziale [2, n]. Idealny na maturę do generowania baz liczb pierwszych.
Sito Eratostenesa
Sprawdzanie każdej liczby po kolei algorytmem pierwiastkowym jest szybkie, ale gdy w zadaniu musimy znaleźć wszystkie liczby pierwsze z przedziału np. od 2 do 1 000 000, nawet metoda z pierwiastkiem zajmie bardzo dużo czasu.
Tu z pomocą przychodzi starożytny Grek, Eratostenes, który wymyślił Sito Eratostenesa. Jest to algorytm działający w oparciu o wykreślanie (odsiewanie) liczb, które na pewno nie są pierwsze.
Jak działa to sito?
Wyobraź sobie, że piszemy na kartce wszystkie liczby od 2 do N.
- Zaczynamy od liczby
2. Zaznaczamy ją jako pierwszą (bo jest), a następnie "wykreślamy" wszystkie jej wielokrotności (4, 6, 8, 10...), bo skoro dzielą się przez 2, na pewno nie są pierwsze. - Bierzemy kolejną niewykreśloną liczbę, czyli
3. Zaznaczamy jako pierwszą i wykreślamy jej wielokrotności (6, 9, 12, 15...). 4jest wykreślone, więc idziemy dalej do5.5nie jest wykreślone. Oznaczamy jako pierwszą i wykreślamy jej wielokrotności.
Robimy to powtarzalnie (tak naprawdę wystarczy iterować do pierwiastka z N). Na sam koniec liczby, które pozostały na kartce niewykreślone – to liczby pierwsze!
Złożoność czasowa Sita to abstrakcyjne O(N log log N), co w praktyce oznacza, że algorytm jest ekstremalnie szybki i działa prawie liniowo.
Implementacja krok po kroku
Najlepiej zaimplementować Sito używając tablicy logicznej o rozmiarze N+1. Wartość True oznacza "liczba pierwsza", a False oznacza "wykreślona".
Python
def sito_eratostenesa(n):
# Tworzymy listę pełną True o rozmiarze n+1
# Zakładamy na początku, że wszystkie liczby są pierwsze
pierwsze = [True] * (n + 1)
# Zera i jedynki od razu skreślamy, bo z definicji nie są pierwsze
pierwsze[0] = False
pierwsze[1] = False
i = 2
# Przechodzimy liczby tylko do pierwiastka z n
while i * i <= n:
if pierwsze[i] == True:
# Skoro i jest pierwsze, to wykreślamy jego wielokrotności
# Zaczynamy od i*i, wykreślając co 'i' kroków
for j in range(i * i, n + 1, i):
pierwsze[j] = False
i += 1
# Wypisywanie liczb, które ocalały (zostały True)
for k in range(2, n + 1):
if pierwsze[k] == True:
print(k, end=" ")
# Wywołanie znajdzie i wypisze liczby pierwsze od 2 do 30
sito_eratostenesa(30)C++
#include <iostream>
#include <vector>
using namespace std;
void sitoEratostenesa(int n) {
// Tworzymy wektor (tablicę) o wielkości n+1 wypełnioną true
vector<bool> pierwsze(n + 1, true);
// 0 i 1 nie są pierwsze
pierwsze[0] = false;
pierwsze[1] = false;
// Lecimy do pierwiastka
for (int i = 2; i * i <= n; i++) {
if (pierwsze[i] == true) {
// Wykreślamy wszystkie wielokrotności liczby i
// Zaczynamy od i*i, zwiększamy skokowo co 'i'
for (int j = i * i; j <= n; j += i) {
pierwsze[j] = false;
}
}
}
// Wypisywanie wyników
for (int k = 2; k <= n; k++) {
if (pierwsze[k] == true) {
cout << k << " ";
}
}
}
int main() {
sitoEratostenesa(30);
return 0;
}[!TIP] Dla dociekliwych: Skąd wzięło się j = i * i ? Dlaczego pętlę wykreślającą zaczynamy od
i * i, a nie odi * 2? Wyobraź sobie, że jesteśmy na kroku wykreślania dla cyfry 5. Jeśli zaczniemy od wielokrotności np.5 * 2, to przecież 10 zostało już wykreślone w kroku dla liczby 2! Liczba 15 (5 * 3) została wykreślona w kroku dla liczby 3. Tak naprawdę pierwszą NIEWYKREŚLONĄ wielokrotnością 5 będzie dopiero5 * 5! To genialna optymalizacja, która pomija zbędne, powtarzające się operacje.
