Sieve of Eratosthenes & prime factorization

RoadmapsC++

Scenario

You need to find all prime numbers up to 1,000,000. If you write a loop checking every number `i` by dividing it by every number from 2 to `sqrt(i)`, the program will take too long and fail.

How can you instantly identify all primes in a massive range without doing any division?

Why it exists

Number theory is a heavy component of mathematical programming and cryptography. Finding primes and prime factors is the foundation for calculating common divisors and understanding large-scale numerical patterns. The Sieve of Eratosthenes provides an elegant, near-linear time pre-computation table that transforms O(sqrt(N)) prime-checking into O(1) lookups.

Mental model

Imagine you have a list of numbers from 2 to 100. You circle 2 (it's prime), then aggressively cross out every multiple of 2 (4, 6, 8...). You then move to the next uncrossed number (3), circle it, and cross out all multiples of 3. By the end, the uncrossed numbers are exactly the primes.

The Sieve of Eratosthenes is an ancient, highly optimized algorithm (O(N log log N)) used to generate all prime numbers up to a limit `N`. Instead of testing if a number is prime (division), it iteratively crosses out composites (multiplication/addition).

Explanation

**The Sieve of Eratosthenes:**

We create a boolean array `is_prime` of size `N+1`, initialized to `true`. We start at 2. If `is_prime[2]` is true, we iterate through its multiples (4, 6, 8...) and mark them `false`. We proceed to the next true number, and repeat.

*Optimization 1:* We only need to check numbers up to `sqrt(N)`. Every composite number up to `N` must have at least one prime factor `<= sqrt(N)`. Thus, checking primes up to `sqrt(N)` safely crosses out all composites up to `N`.

*Optimization 2:* When crossing out multiples for a prime `p`, we can start crossing out at `p * p`. All smaller multiples (`p * 2`, `p * 3`) were already crossed out by smaller primes.

**Prime Factorization (O(sqrt(N))):**

To find the prime factors of a single number `N`, we don't need the Sieve. We just loop `d` from 2 to `sqrt(N)`. While `N` is divisible by `d`, we add `d` to our factors and divide `N` by `d`. If `N > 1` at the very end, that leftover `N` is itself a prime factor.

*(Note: If you need to factorize 100,000 different numbers, you can modify the Sieve to store the Smallest Prime Factor (SPF) for each number. To factorize, repeatedly divide the query number by its SPF (`N = N / SPF[N]`) until it becomes 1. This takes O(log N) time per query!)*

**Space Complexity Warning:**

The Sieve requires `O(N)` memory. An array of size 10^9 requires ~1GB of RAM, exceeding typical limits. The standard Sieve is only viable up to `N ~ 10^7`.

Code examples

Sieve of Eratosthenes

vector<bool> sieve(int n) {
    vector<bool> is_prime(n + 1, true);
    is_prime[0] = is_prime[1] = false;
    
    // Optimization 1: Loop up to sqrt(n)
    for (int p = 2; p * p <= n; p++) {
        if (is_prime[p]) {
            // Optimization 2: Start crossing out at p*p
            for (int i = p * p; i <= n; i += p) {
                is_prime[i] = false;
            }
        }
    }
    return is_prime;
}

This computes all primes up to N in O(N log log N) time, which is practically linear. `N = 10^7` computes in fractions of a second.

Prime Factorization of a Single Number

vector<long long> prime_factors(long long n) {
    vector<long long> factors;
    for (long long d = 2; d * d <= n; d++) {
        while (n % d == 0) {
            factors.push_back(d);
            n /= d;
        }
    }
    // If n is a prime greater than the loop limit
    if (n > 1) {
        factors.push_back(n);
    }
    return factors;
}

Calculates the prime factors of N in O(sqrt(N)) time. This works efficiently for isolated numbers up to 10^14.

Key points

Common mistakes

Recall questions

Questions & answers

A developer runs the Sieve of Eratosthenes up to `N = 10^9`. Their code gets a 'Memory Limit Exceeded' error, or a Segmentation Fault. Why?

An array of size 10^9 requires massive contiguous memory allocation. Even as a `vector<bool>` (which optimizes to 1 bit per boolean), it requires ~125 MB, and standard arrays would require 1-4 GB. The standard Sieve should only be used for N up to ~10^7.

Approach: Recognize the space complexity constraints of O(N) memory algorithms.

You are given 100,000 queries, each asking for the prime factorization of a number up to 10^7. Using the O(sqrt(N)) factorization loop for each query results in TLE. How can you optimize this?

Modify the Sieve of Eratosthenes to store the Smallest Prime Factor (SPF) for every number up to 10^7. Then, for each query, repeatedly divide the number by its SPF. This reduces factorization from O(sqrt(N)) to O(log N) per query.

Approach: Understand how to augment standard algorithms for bulk querying.

Return to C++ Roadmap