L6. Sieve of Eratosthenes | Maths Playlist
A comprehensive study note on the Sieve of Eratosthenes algorithm, transitioning from naive prime-checking approaches to an ultra-efficient precomputation strategy for generating all prime numbers up to .
1. Why Do We Need This? (Problem Statement & Status Quo)
The fundamental problem tackled in this chapter is straightforward: Given a number , print all prime numbers up to .
- Example: If , the expected output is
2, 3, 5, 7. There are four prime numbers up to . - The Naive Approach:
- Iterate from up to .
- For each integer , invoke a helper function to check if it is prime (which takes time).
- Time Complexity of Naive Approach:
- Space Complexity:
The Bottleneck in Production
Imagine . Running a loop up to and executing a square-root prime check for each integer results in an extraordinarily expensive operation ( operations). For , the execution time becomes unacceptably sluggish.
To solve this, we need to eliminate the per-number prime check and achieve constant-time lookups for any number's primality status.
2. Core Mental Model: The Elimination Paradigm
Instead of testing whether every individual number is prime from scratch, the Sieve of Eratosthenes flips the perspective: If a number is prime, all of its multiples are guaranteed to be composite (non-prime).
Real-Life Analogy: The Strike-Off Board
Imagine sitting in a classroom with a numbered grid from to written on a blackboard.
- You walk to the first number (), realize it's circled (prime), and immediately walk down the rows crossing out every second number () because they are multiples of .
- You move to the next uncrossed number (), circle it, and cross out every third number ().
- By systematically walking through and eliminating multiples, you never have to "test" remaining numbers; whatever survives uncrossed at the end is strictly a prime.
3. Step-by-Step Walkthrough ()
Let us trace how the algorithm processes up to using an array of size (omitting indices and ).
- Initialization: Declare a
primearray of size . Initialize every index from to with1(representing "tentatively prime"). - Processing :
- is marked as prime (
1). - Strike off all multiples of by setting them to
0:4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30become0.
- is marked as prime (
- Processing :
- is still
1(prime). - Strike off its multiples starting from :
6, 9, 12, 15, 18, 21, 24, 27, 30become0.
- is still
- Processing :
- Index is already
0(marked earlier by ). Since it is not prime, we skip striking off its multiples (because all multiples of are already multiples of ).
- Index is already
- Processing :
- is
1(prime). Strike off its multiples:10, 15, 20, 25, 30. ( is newly marked0).
- is
Final State of Primes up to
After evaluating up to , the indices remaining marked as 1 are:
2, 3, 5, 7, 11, 13, 17, 19, 23, 29.
4. Naive Sieve Code vs. Optimized Sieve Code
Naive Implementation (Unoptimized Multiples Loop)
void simpleSieve(int n) {
vector<int> prime(n + 1, 1);
prime[0] = prime[1] = 0;
for (int i = 2; i <= n; i++) {
if (prime[i] == 1) {
// Starts marking from 2 * i
for (int j = 2 * i; j <= n; j += i) {
prime[j] = 0;
}
}
}
}
Critical Inefficiencies in Naive Code
- When , the inner loop starts at
5 * 2 = 10, then5 * 3 = 15, then5 * 4 = 20. - However,
10was already marked by (2 * 5), and20was already marked by (2 * 10). Redundantly visiting already-crossed numbers wastes CPU cycles.
5. Optimizing the Sieve (Two Crucial Enhancements)
To make the algorithm production-ready, we introduce two mathematical optimizations:
Optimization 1: Start Inner Loop from
Instead of starting multiples at , we can start directly at .
- Why? Any multiple smaller than (like ) would have already been marked by a smaller prime factor.
- Example for : was handled by , was handled by . The first fresh multiple that only gets marked by is .
Optimization 2: Outer Loop Boundary at
Instead of running the outer loop up to , we only need to run it up to (or check ).
- Why? If , . Once , , which exceeds . Thus, numbers greater than cannot have unvisited composite multiples within range .
6. Complete Optimized Code Implementation
Here is the clean, optimized implementation of the Sieve of Eratosthenes across popular languages:
C++ Implementation
#include <iostream>
#include <vector>
using namespace std;
void sieveOfEratosthenes(int n) {
// Step 1: Create a prime array of size n + 1, initialized to 1 (true)
vector<int> prime(n + 1, 1);
prime[0] = prime[1] = 0;
// Step 2: Loop from 2 up to sqrt(n)
for (int i = 2; i * i <= n; i++) {
if (prime[i] == 1) {
// Step 3: Mark all multiples of i starting from i * i
for (int j = i * i; j <= n; j += i) {
prime[j] = 0;
}
}
}
// Step 4: Print all prime numbers
cout << "Primes up to " << n << ": ";
for (int i = 2; i <= n; i++) {
if (prime[i] == 1) {
cout << i << " ";
}
}
cout << endl;
}
int main() {
int n = 30;
sieveOfEratosthenes(n);
return 0;
}
Python Implementation
def sieve_of_eratosthenes(n):
prime = [1] * (n + 1)
prime[0] = prime[1] = 0
i = 2
while i * i <= n:
if prime[i] == 1:
for j in range(i * i, n + 1, i):
prime[j] = 0
i += 1
primes = [i for i in range(2, n + 1) if prime[i] == 1]
return primes
print(sieve_of_eratosthenes(30))
7. Complexity Analysis
8. Summary and Key Takeaways
- Elimination over Testing: The Sieve of Eratosthenes bypasses expensive trial division by systematically striking out multiples of known primes.
- Constant-Time Black Box: Through one-time precomputation, primality queries for any number up to are reduced from down to .
- Optimization: Inner loops for any prime do not need to start at ; they can safely begin at because smaller multiples are already eliminated by prior primes.
- Outer Loop Boundary: The outer loop execution terminates at because higher prime factors have already exhausted their composite footprints within .
- Sub-Linear Growth: The time complexity of makes this algorithm exceptionally fast even for large inputs such as .
- Space-Time Tradeoff: We trade extra memory ( array storage) for massive speedups in computational throughput.