#include #define MAX 1000001 int sieve[MAX]; /* Why do we use such a big number? Because we don't know how big the interval is, we give as much freedom as possible; note that this number may not work for your computer, and you will need to make it smaller, or if it works, you can make it bigger. */ /* [PROBLEM TO SOLVE]: Given a number "n", find all prime numbers till "n" inclusive. A prime number is a number which has only 2 divisors: 1 and itself. [EXAMPLE]: n = 22 -> The numbers are: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 -> The prime numbers are: 2 3 5 7 11 13 17 19 [APPROACH]: Sieve Of Eratosthenes algorithm [IDEA]: -> We have to analyze numbers from 2 to n. So, firstly, we write them down: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ... n -> Next, we will do something which is called marking. To illustrate this, I will put "*" below the numbers. -> We begin from 2 till n, and for every number, we mark all its multiples till n inclusive. -> We are at 2, so we mark 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26 and all numbers 2 * k, with k > 1 and stop when 2 * k > n. 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ... n * * * * * * * * * * * * -> We move at 3 so we mark 6, 9, 12, 15, 18, 21, 24 and all numbers 3 * k, with k > 1 and stop when 3 * k > n. 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ... n * * * * * * * * * * * * * * * -> We continue this process by taking every number till n and marking all its multiples till n inclusive. -> By the end of this algorithm, the numbers that remained unmarked are only prime numbers. [TIME COMPLEXITY]: For every i till sqrt(n), we perform n / i operations, where i is prime. The ecuation is: sqrt(n) * (n/3 + n/4 + n/5 + n/6 + n/7 + ...) = sqrt(n) * n * (1/3 + 1/4 + 1/5 + 1/6 + 1/7 + ...) The third factor, according to Euler, grows the same as ln(ln(n)) So the time complexity is: O(sqrt(n) * n * ln(ln(n))). [SPACE COMPLEXITY]: O(n) */ void sieve_of_eratosthenes(unsigned long long n) { sieve[0] = sieve[1] = 1; // we know that 0 and 1 are already pries so we mark them for (unsigned long long i = 4; i <= n; i += 2) sieve[i] = 1; // We know that every even number greater than 2 is not prime. // We can mark in advance these numbers by beginning from 4 and // iterating from 2 to 2. for (unsigned long long i = 3; i * i <= n; i += 2) // it is enough to iterate till sqrt(n) { // marking numbers for (unsigned long long j = i * i; j <= n; j += 2 * i) sieve[j] = 1; // We can begin from i * i because all numbers till i * i have been already marked // We iterate with j += 2 * i to avoid even numbers marked before } /* In Sieve of Eratosthenes, we use a global array, and global variables are automatically initialized with 0. So we use that to our advantage instead of manually setting all memory spaces with 0. We are doing marking like this: 0 - prime 1 - not prime Why? I know that using 1 for primes and 0 for not primes would have been more intuitive, but declaring an array in global scope, it got initialized with 0, so it is just a waste to overwrite all memory spaces with 1. */ } // Utility function to print prime numbers void print_prime_numbers(unsigned long long n) { for (unsigned long long i = 2; i <= n; ++i) { if (sieve[i] == 0) printf("%llu ", i); // if number is not marked (it is a prime number) we print it } } // Driver program int main() { unsigned long long n; printf("Enter number: "); scanf("%llu", &n); sieve_of_eratosthenes(n); printf("\n\nPrime numbers are:\n"); print_prime_numbers(n); printf("\n"); } /* [SAMPLE INPUT 1]: 22 Output: 2 3 5 7 11 13 17 19 [SAMPLE INPUT 2]: 46 Output: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 [SAMPLE INPUT 3]: 100 Output: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 */ /* [OBSERVATIONS]: -> Note that I used unsigned long long for variables, and operations like raising to power can easily exceed unsigned long long, so be careful with input numbers. -> You can play around with #define MAX 1000000001 and #define MAX_PRIMES 100000001. Try to find which is the maximum limit accepted by your computer. -> To make this algorithm space-efficient, you can change to C++ and use a vector container from the STL library like this [vector vector_name]. Before that, include vector library like #include . That container is not a regular one. It is a memory-optimization of template classes like vector , which uses only n/8 bytes of memory. Many modern processors work faster with bytes because they can be accessed directly. Template class vector works directly with bits. But these things require knowledge about template classes, which are very powerful in C++. -> This algorithm can be optimized using bits operations to achieve linear time. But ln(ln(n)) can be approximated to O(1). */