Merge branch 'MakeContributions:main' into main
commit
84e2db93f7
|
@ -7,6 +7,7 @@
|
|||
- [Reverse an array](arrays/reverse-array.c)
|
||||
- [Maximum difference](arrays/maximum-difference.c)
|
||||
- [Largest Element](arrays/largestElement.c)
|
||||
- [Sieve of Eratosthenes](arrays/sieve-of-eratosthenes.c)
|
||||
|
||||
## Bit Manipulation
|
||||
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#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 <bool> vector_name]. Before that, include vector library like #include <vector>. That
|
||||
container is not a regular one. It is a memory-optimization of template classes like vector <T>,
|
||||
which uses only n/8 bytes of memory. Many modern processors work faster with bytes because they
|
||||
can be accessed directly. Template class vector <T> 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).
|
||||
*/
|
Loading…
Reference in New Issue