średniAlgorytmyPythonC++

Sito Eratostenesa - Generowanie Liczb Pierwszych na Maturze

15 min czytania
Python, C++
Zaktualizowano: 2.11.2025

Sito Eratostenesa to genialny w swej prostocie i niesamowicie szybki algorytm do znajdowania wszystkich liczb pierwszych w zadanym zakresie (np. od 2 do n). Zamiast sprawdzać każdą liczbę z osobna (co byłoby wolne, metodą O(n√n)), algorytm ten działa jak sito: 'przesiewa' liczby. Wyobraź sobie, że masz listę wszystkich liczb od 2 do n. Bierzesz pierwszą z nich (2) i wykreślasz wszystkie jej wielokrotności (4, 6, 8, 10...). Następnie bierzesz pierwszą kolejną niewykreśloną liczbę (3) i robisz to samo (wykreślasz 6, 9, 12, 15...). Powtarzasz ten proces. Liczby, które zostaną niewykreślone na końcu, to liczby pierwsze. To metoda 'wykreślania' wielokrotności, a nie 'sprawdzania' podzielności każdej liczby.

Dlaczego to ważne? Sito jest kluczowe, gdy zadanie maturalne wymaga wielokrotnego sprawdzania pierwszości lub faktoryzacji (rozkładu na czynniki pierwsze) dla wielu liczb z pliku. Zamiast pisać wolną funkcję 'czy_pierwsza(n)' i wywoływać ją 1000 razy w pętli, budujesz sito raz na początku programu (co jest bardzo szybkie), a potem masz natychmiastowy dostęp (złożoność O(1)) do informacji, czy dana liczba jest pierwsza. To fundamentalna technika optymalizacyjna.

Teoria

Główną ideą jest 'wykreślanie' wielokrotności. Tworzymy tablicę logiczną (boolean) `is_prime` o rozmiarze `n+1`, gdzie na początku zakładamy, że wszystkie liczby są pierwsze (`True`). Następnie iterujemy od 2 do `√n`. Jeśli napotkamy liczbę `p`, która wciąż jest oznaczona jako `True`, oznacza to, że jest pierwsza. Wtedy wykreślamy (ustawiamy na `False`) wszystkie jej wielokrotności.

Jak to działa?

  1. Krok 1: Stwórz tablicę logiczną (np. listę w Pythonie) `is_prime` o rozmiarze `n + 1` i wypełnij ją wartościami `True`.
  2. Krok 2: Ustaw `is_prime[0]` oraz `is_prime[1]` na `False`, ponieważ 0 i 1 nie są liczbami pierwszymi.
  3. Krok 3: Rozpocznij pętlę zewnętrzną dla `p` od 2 do `√n` (pierwiastek z n - to kluczowa optymalizacja).
  4. Krok 4: W pętli, sprawdź `if is_prime[p] == True` (jeśli `p` nie zostało wcześniej wykreślone, to musi być liczbą pierwszą).
  5. Krok 5: Jeśli warunek jest spełniony, uruchom pętlę wewnętrzną, aby wykreślić wszystkie wielokrotności `p`.
  6. Krok 6: Pętla wewnętrzna powinna iterować od `p*p` (kolejna optymalizacja) do `n`, z krokiem `p`. Dla każdego `i` w tej pętli, ustaw `is_prime[i] = False`.
  7. Krok 7: Po zakończeniu pętli zewnętrznej, tablica `is_prime` jest gotowa. Możesz teraz odczytać liczby pierwsze, np. iterując od 2 do `n` i sprawdzając, które indeksy mają wartość `True`.

Złożoność: Złożoność czasowa: O(n log log n). To jest bardzo bliskie złożoności liniowej O(n), co czyni ten algorytm ekstremalnie wydajnym. Dzieje się tak, ponieważ większość liczb jest wykreślana tylko raz lub bardzo niewiele razy. Złożoność przestrzenna (pamięciowa) to O(n), ponieważ musimy przechować tablicę logiczną o rozmiarze `n`.

Implementacja

Standardowe Sito Eratostenesa

Python
def sito(n):
    """Generuje sito Eratostenesa do liczby n."""
    # 1. Stwórz tablicę i zainicjuj ją jako True
    is_prime = [True] * (n + 1)

    # 2. 0 i 1 nie są pierwsze
    is_prime[0] = is_prime[1] = False

    # 3. Pętla zewnętrzna do √n
    for p in range(2, int(n**0.5) + 1):
        # 4. Jeśli p jest pierwsze (nie zostało wykreślone)
        if is_prime[p]:
            # 5. Wykreśl wielokrotności p, zaczynając od p*p
            for i in range(p * p, n + 1, p):
                is_prime[i] = False

    # Opcjonalnie: Zwróć listę liczb pierwszych
    primes = []
    for i in range(2, n + 1):
        if is_prime[i]:
            primes.append(i)

    return is_prime, primes

# Przykład użycia do znalezienia liczb pierwszych do 30
is_prime_do_30, primes_do_30 = sito(30)
print(f"Liczby pierwsze do 30: {primes_do_30}")
print(f"Czy 17 jest pierwsza? {is_prime_do_30[17]}")
print(f"Czy 18 jest pierwsza? {is_prime_do_30[18]}")

To jest klasyczna, zoptymalizowana implementacja sita. Pętla zewnętrzna 'for p...' idzie tylko do pierwiastka z 'n' (√n). Pętla wewnętrzna 'for i...' zaczyna wykreślanie od 'p*p', ponieważ wszystkie mniejsze wielokrotności (jak 2*p, 3*p) zostały już wykreślone przez mniejsze liczby pierwsze.

Sito Eratostenesa w C++ (używając vector<bool>)

C++
#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

vector<bool> sito(int n) {
    // 1. Stwórz wektor i zainicjuj go jako true
    vector<bool> is_prime(n + 1, true);

    // 2. 0 i 1 nie są pierwsze
    is_prime[0] = is_prime[1] = false;

    // 3. Pętla zewnętrzna do √n
    for (int p = 2; p * p <= n; p++) {
        // 4. Jeśli p jest pierwsze
        if (is_prime[p]) {
            // 5. Wykreśl wielokrotności p, zaczynając od p*p
            for (int i = p * p; i <= n; i += p)
                is_prime[i] = false;
        }
    }
    return is_prime;
}

int main() {
    int n = 30;
    vector<bool> is_prime_do_30 = sito(n);

    cout << "Liczby pierwsze do 30: ";
    for (int i = 2; i <= n; i++) {
        if (is_prime_do_30[i]) {
            cout << i << " ";
        }
    }
    cout << endl;
    cout << "Czy 17 jest pierwsza? " << is_prime_do_30[17] << endl; // Wypisze 1 (true)
    cout << "Czy 18 jest pierwsza? " << is_prime_do_30[18] << endl; // Wypisze 0 (false)
    return 0;
}

Logika jest identyczna jak w Pythonie. Używamy `vector<bool>`, który jest wydajną implementacją tablicy logicznej w C++. Zamiast `int(n**0.5) + 1` możemy użyć wydajniejszego warunku w pętli: `p * p <= n`.

Przykładowe Zadania Maturalne

Matura 2026Zadanie Zadanie Typu Maturalnego 1

W pliku 'liczby.txt' znajduje się 1000 liczb naturalnych (każda w osobnej linii) z zakresu od 1 do 1 000 000. Napisz program, który policzy, ile z tych liczb to liczby pierwsze.

Wskazówka: To klasyczny problem wydajnościowy. Nie pisz funkcji 'is_prime()' i nie wywołuj jej 1000 razy w pętli, bo program będzie działał zbyt wolno. Zamiast tego, przed otwarciem pliku, zbuduj jedno Sito Eratostenesa do 1 000 000. Następnie dla każdej liczby z pliku po prostu sprawdź w gotowej tablicy (np. 'if sito[liczba]: ...'), co ma złożoność O(1).

Pokaż szkic rozwiązania
1. Zdefiniuj funkcję `sito(n)`, która zwraca tablicę boolean `is_prime` o rozmiarze `n+1`.
2. Wywołaj tę funkcję raz: `is_prime_tab = sito(1000000)`.
3. Otwórz plik 'liczby.txt' do odczytu.
4. Zainicjuj 'licznik_pierwszych = 0'.
5. Dla każdej linii w pliku:
   a. Wczytaj 'liczba = int(linia.strip())'.
   b. Sprawdź warunek: 'if is_prime_tab[liczba]:'.
   c. Jeśli 'True', zwiększ licznik: 'licznik_pierwszych += 1'.
6. Wypisz 'licznik_pierwszych'.
Matura 2024Zadanie 3.2 (Matura Próbna Grudzień 2024)

W pliku liczby.txt jest danych 2000 liczb całkowitych z zakresu [1000, 9999]. Wypisz wszystkie liczby z pliku liczby.txt, które mają co najmniej 5 różnych dzielników pierwszych.

Wskazówka: To zadanie wymaga efektywnego rozkładu na czynniki pierwsze. Najpierw wygeneruj listę liczb pierwszych (np. sitem do 100, bo 9999 < 100²). Następnie dla każdej liczby z pliku, zlicz jej różne dzielniki pierwsze, dzieląc ją przez kolejne liczby z Twojej listy.

Pokaż szkic rozwiązania
1. Zbuduj sito `is_prime` do N=9999 i stwórz listę `primes` zawierającą liczby pierwsze.
2. Otwórz plik 'liczby.txt'.
3. Dla każdej liczby `n` z pliku:
   a. Zainicjuj `licznik_roznych_dzielnikow = 0`.
   b. Zrób kopię liczby: `temp_n = n`.
   c. Rozpocznij pętlę po liście `primes` (dla każdego `p` z `primes`):
   d.    Jeśli `p * p > temp_n`: break (jeśli `temp_n` jest > 1, to samo jest czynnikiem pierwszym).
   e.    Jeśli `temp_n % p == 0` (znaleziono dzielnik pierwszy):
   f.       `licznik_roznych_dzielnikow += 1`.
   g.       Dopóki `temp_n % p == 0`: `temp_n = temp_n // p` (usuń wszystkie wystąpienia `p`).
   h.  Jeśli po pętli `temp_n > 1` (oznacza to, że został ostatni czynnik pierwszy, większy niż √n):
   i.    `licznik_roznych_dzielnikow += 1`.
   j.  Jeśli `licznik_roznych_dzielnikow >= 5`: Wypisz `n`.

Częste Błędy

Pisanie funkcji 'is_prime' zamiast sita

Największy błąd maturalny. Uczeń pisze funkcję `is_prime(k)` (która działa w czasie O(√k)) i wywołuje ją w pętli 1000 razy. Dla dużych liczb przekracza to limit czasu. Sito buduje się raz w czasie O(n log log n) i potem sprawdza pierwszość w O(1).

Poprawka: Jeśli zadanie wymaga sprawdzenia pierwszości wielu liczb, zawsze użyj sita. Zbuduj je raz na początku programu.

Zła optymalizacja pętli zewnętrznej

Pętla zewnętrzna (iterująca po `p`) idzie niepotrzebnie do `n` (`range(2, n+1)`). Wystarczy iść do pierwiastka z `n` (`range(2, int(n**0.5) + 1)`), bo każda liczba złożona `x` musi mieć przynajmniej jeden czynnik pierwszy mniejszy lub równy `√x`.

Poprawka: Pętlę zewnętrzną sita zawsze optymalizuj do pierwiastka z `n`.

Zły start pętli wewnętrznej (wykreślającej)

Uczniowie często piszą pętlę wewnętrzną od `2*p` (`range(2*p, n+1, p)`). To działa, ale jest wolniejsze. Lepsza optymalizacja to start od `p*p` (`range(p*p, n+1, p)`), ponieważ wszystkie mniejsze wielokrotności (jak 2*p, 3*p...) zostały już wykreślone przez mniejsze liczby pierwsze (2, 3...).

Poprawka:

Zapominanie o 0 i 1

Tablica `is_prime` jest indeksowana od 0. Uczniowie zapominają, że 0 i 1 nie są liczbami pierwszymi i zostawiają `is_prime[0]` i `is_prime[1]` jako `True`, co psuje późniejsze testy.

Poprawka: Zawsze na początku algorytmu ustaw `is_prime[0] = False` i `is_prime[1] = False`.

Błąd 'off-by-one' (rozmiar tablicy)

Jeśli chcesz znaleźć liczby pierwsze do n włącznie, potrzebujesz tablicy o rozmiarze `n+1` (bo indeksy idą od 0 do `n`). Częsty błąd to stworzenie tablicy `[True] * n`, co daje indeksy tylko do `n-1`.

Poprawka: Zawsze twórz tablicę o rozmiarze `n + 1`, np. `is_prime = [True] * (n + 1)`.

Kluczowe Wnioski

  • Sito Eratostenesa służy do szybkiego generowania wszystkich liczb pierwszych w zakresie [2, n].
  • Jest to algorytm 'wykreślający' wielokrotności, a nie 'sprawdzający' każdą liczbę.
  • Jego złożoność czasowa to O(n log log n) - bardzo blisko liniowej.
  • Na maturze: buduj sito RAZ, jeśli masz sprawdzić pierwszość WIELU liczb.
  • Kluczowa optymalizacja 1: Pętla zewnętrzna (po `p`) idzie tylko do `√n`.
  • Kluczowa optymalizacja 2: Pętla wewnętrzna (wykreślająca) zaczyna od `p*p`.
  • Pamiętaj o ustawieniu `is_prime[0]` i `is_prime[1]` na `False`.

Najczęściej Zadawane Pytania

Dlaczego pętla zewnętrzna idzie tylko do pierwiastka z n?

Ponieważ każda liczba złożona 'x' musi mieć co najmniej jeden dzielnik pierwszy mniejszy lub równy '√x'. Jeśli liczba 'p' (większa od '√n') nie została wykreślona, to musi być pierwsza, bo jej wielokrotności (np. 2*p) byłyby większe od 'n' lub zostałyby już wykreślone przez mniejszy czynnik.

Dlaczego pętla wewnętrzna zaczyna od p*p?

To optymalizacja. Nie musimy wykreślać 2*p, 3*p, ..., (p-1)*p. Dlaczego? Bo te liczby mają mniejszy dzielnik pierwszy niż 'p' (np. 2, 3, ...). Zostały one już wykreślone przez pętle dla 2, 3 itd. Pierwszą wielokrotnością 'p', która na pewno nie została wcześniej wykreślona, jest 'p*p'.

Czy sito jest lepsze niż funkcja is_prime(n)?

To zależy. Jeśli masz sprawdzić jedną dużą liczbę, funkcja `is_prime(n)` (działająca w O(√n)) jest lepsza. Ale jeśli masz sprawdzić tysiące liczb (jak na maturze), zbudowanie sita raz (w O(n log log n)) i sprawdzanie w O(1) jest wielokrotnie szybsze.

Co jeśli potrzebuję liczb pierwszych w zakresie [a, b] a nie [2, n]?

Najprościej jest zbudować sito do 'b' (`sito(b)`) i po prostu zebrać liczby pierwsze, które są większe lub równe 'a'. Istnieje bardziej skomplikowane 'sito segmentowe', ale na maturę standardowe sito do 'b' w zupełności wystarczy.

Chcesz opanować wszystkie tematy maturalne?

Dołącz do kursu i zyskaj dostęp do interaktywnych lekcji, edytora kodu i setek zadań.

Powiązane Tematy