Question

I am studying Elements of Programming Interviews, and I am stuck on a problem. It is about writing a c++ function for finding all prime numbers from 1 to n, for a given n.

vector<int> generate_primes_from_1_to_n(const int &n) {
    int size = floor(0.5 * (n - 3)) + 1;
    // is_prime[i] represents (2i+3) is prime or not

    vector<int> primes; // stores the primes from 1 to n

    primes.push_back(2);
    vector<bool> is_prime(size, true);

    for(long i = 0; i < size; ++i) {
       if(is_prime[i]) {
           int p = (i << 1) + 3;
           primes.push_back(p);
           // sieving from p^2, whose index is 2i^2 + 6i + 3
           for (long j = ((i * i) << 1) + 6 * i + 3; j < size; j += p) {
               is_prime[j] = false;
           }
       }
    }
}

Particularly, I cannot understand the commented 'sieving from p^2, whose index is 2i^2 + 6i + 3' part. For the other parts, I can kind of grasp a rough idea of how they work, but I don't know where this '2i^2 + 6i + 3' comes from, what it does, and how that and its related pieces of codes work.

Can anyone explain this code better? Thank you.

+

I am getting this output(+'cout's to understand it better)

./a.out 100
size is: 49
for i = 0 is_prime[i] is 1
pushing back p of size 3
((i * i) << 1) + 6 * i + 3 for i of 0 is 3
((i * i) << 1) + 6 * i + 3 for i of 0 is 6
((i * i) << 1) + 6 * i + 3 for i of 0 is 9
((i * i) << 1) + 6 * i + 3 for i of 0 is 12
((i * i) << 1) + 6 * i + 3 for i of 0 is 15
((i * i) << 1) + 6 * i + 3 for i of 0 is 18
((i * i) << 1) + 6 * i + 3 for i of 0 is 21
((i * i) << 1) + 6 * i + 3 for i of 0 is 24
((i * i) << 1) + 6 * i + 3 for i of 0 is 27
((i * i) << 1) + 6 * i + 3 for i of 0 is 30
((i * i) << 1) + 6 * i + 3 for i of 0 is 33
((i * i) << 1) + 6 * i + 3 for i of 0 is 36
((i * i) << 1) + 6 * i + 3 for i of 0 is 39
((i * i) << 1) + 6 * i + 3 for i of 0 is 42
((i * i) << 1) + 6 * i + 3 for i of 0 is 45
((i * i) << 1) + 6 * i + 3 for i of 0 is 48
for i = 1 is_prime[i] is 1
pushing back p of size 5
((i * i) << 1) + 6 * i + 3 for i of 1 is 11
((i * i) << 1) + 6 * i + 3 for i of 1 is 16
((i * i) << 1) + 6 * i + 3 for i of 1 is 21
((i * i) << 1) + 6 * i + 3 for i of 1 is 26
((i * i) << 1) + 6 * i + 3 for i of 1 is 31
((i * i) << 1) + 6 * i + 3 for i of 1 is 36
((i * i) << 1) + 6 * i + 3 for i of 1 is 41
((i * i) << 1) + 6 * i + 3 for i of 1 is 46
for i = 2 is_prime[i] is 1
pushing back p of size 7
((i * i) << 1) + 6 * i + 3 for i of 2 is 23
((i * i) << 1) + 6 * i + 3 for i of 2 is 30
((i * i) << 1) + 6 * i + 3 for i of 2 is 37
((i * i) << 1) + 6 * i + 3 for i of 2 is 44
for i = 3 is_prime[i] is 0
for i = 4 is_prime[i] is 1
pushing back p of size 11
for i = 5 is_prime[i] is 1
pushing back p of size 13
for i = 6 is_prime[i] is 0
for i = 7 is_prime[i] is 1
pushing back p of size 17
for i = 8 is_prime[i] is 1
pushing back p of size 19
for i = 9 is_prime[i] is 0
for i = 10 is_prime[i] is 1
pushing back p of size 23
for i = 11 is_prime[i] is 0
for i = 12 is_prime[i] is 0
for i = 13 is_prime[i] is 1
pushing back p of size 29
for i = 14 is_prime[i] is 1
pushing back p of size 31
for i = 15 is_prime[i] is 0
for i = 16 is_prime[i] is 0
for i = 17 is_prime[i] is 1
pushing back p of size 37
for i = 18 is_prime[i] is 0
for i = 19 is_prime[i] is 1
pushing back p of size 41
for i = 20 is_prime[i] is 1
pushing back p of size 43
for i = 21 is_prime[i] is 0
for i = 22 is_prime[i] is 1
pushing back p of size 47
for i = 23 is_prime[i] is 0
for i = 24 is_prime[i] is 0
for i = 25 is_prime[i] is 1
pushing back p of size 53
for i = 26 is_prime[i] is 0
for i = 27 is_prime[i] is 0
for i = 28 is_prime[i] is 1
pushing back p of size 59
for i = 29 is_prime[i] is 1
pushing back p of size 61
for i = 30 is_prime[i] is 0
for i = 31 is_prime[i] is 0
for i = 32 is_prime[i] is 1
pushing back p of size 67
for i = 33 is_prime[i] is 0
for i = 34 is_prime[i] is 1
pushing back p of size 71
for i = 35 is_prime[i] is 1
pushing back p of size 73
for i = 36 is_prime[i] is 0
for i = 37 is_prime[i] is 0
for i = 38 is_prime[i] is 1
pushing back p of size 79
for i = 39 is_prime[i] is 0
for i = 40 is_prime[i] is 1
pushing back p of size 83
for i = 41 is_prime[i] is 0
for i = 42 is_prime[i] is 0
for i = 43 is_prime[i] is 1
pushing back p of size 89
for i = 44 is_prime[i] is 0
for i = 45 is_prime[i] is 0
for i = 46 is_prime[i] is 0
for i = 47 is_prime[i] is 1
pushing back p of size 97
for i = 48 is_prime[i] is 0

This does not make sense to me, either.

For example, why for p=5, it starts removing it from 11, not 5^2 = 25, in the lines below? pushing back p of size 5 ((i * i) << 1) + 6 * i + 3 for i of 1 is 11

Also, isn't 11 a prime? It is really confusing. Please help me. Thank you.

Était-ce utile?

La solution

Algorithm

The algorithm used by your prime generator code is called "Sieve of Eratosthenes". In general, it creates a list of numbers, and iterates over the list. All multiplications of the current number are removed from the list, and the remaining numbers are prime.

For example, let us consider [2,3,4,5,6,7,8,9,10,11,12,13,14,15]. We encounter 2, so we remove all even numbers from the list:

[2,3,5,7,9,11,13,15]

Same for 3:

[2,3,5,7,11,13]

5, 7, 11 and 13 have no multiplications in the list, so we remove nothing and remain with a list of primes.

Visual example

enter image description here

In this example (courtesy of Wikipedia), all multiplications of 2, 3 and 5 were removed from the list - multiplications of 2 were painted pink, multiplication of 3 were painted in green, and multiplications of 5 in dark blue. 7 will be iterated next, so it's highlighted. The dark colored numbers are prime, the light colored numbers are not prime, and the gray numbers have net yet been reached.

Optimizations in your code

As mentioned by @BenJackson, your code is optimized twice:

  • Firstly, it only stores odd numbers only starting from 3, because we know that even numbers are not prime (except 2).
  • Secondly, for the number p, it only starts to sieve from p². This is correct because if n<p then n*p was already sieved when multiplications of n were sieved.

This is the reason why the cryptic comment:

// sieving from p^2, whose index is 2i^2 + 6i + 3

Suppose that our algorithm has reached the second item in the vector, therefore i=2. The number in question is 5, because the index i denotes the number 2i+3 (first optimization).

We should sieve all multiplications of 5 from 25 onwards. The index that represents 25 is 11, because 25=2*11+3. Following your printouts, it removes indices 11, 16, 21, 26, ..., which correspond to the numbers 25, 35, 45, 55, .. - all odd multiplications of 5 we'd like to remove.

You can read more about it at Wikipedia or Wolfram MathWorld, and there's a nice javascript on-line demonstration here.

Autres conseils

The table of primes only stores odd values starting at 3 (obviously even values cannot be prime). The relation is given in the line int p = (i << 1) + 3, or p = 2i + 3. Now solve that for i, getting i = (p - 3)/2. Now what i corresponds to p^2? Plug in (2i+3)^2 to that second formula and simplify. Now you have the i for p^2 in terms of the i for p.

Example: Let's say i=1, thus the entry is_prime[i] is a test of the prime p=2i+3, or p=5. So yes, it is prime. Now the seive (explained elsewhere) wants to start marking non-primes at 25. It needs to know what i corresponds to 25. Now you could simply compute p*p and then insert that into i=(p-3)/2 and get j=11. The code has skipped those intermediate steps (as I showed above) to compute j=2i^2+6i+3 and gotten j=11 directly.

Lines like these:

vector<int> generate_primes_from_1_to_n(const int &n) {
    int size = floor(0.5 * (n - 3)) + 1;

...

for(long i = 0; i < size; ++i) {
   if(is_prime[i]) {
       int p = (i << 1) + 3;

are a 'hack' so that the potential prime numbers 3, 5, 7, etc can be iterated over by iterating 0, 1, 2, 3 in i and using p for the corresponding possible primes 3, 5, 7, 9, etc.

It's a basic prime sieve other than that.

As others have noted, i maps array locations and p maps the numbers that those array locations represent, according to the formula p = 2 i + 1. It may be easier to think about if you keep i and p explicitly in the program:

function primes(n)
    m := floor((n-1)/2)
    sieve := makeArray(0..m-1, True)
    i := 0; p := 3; ps := [2]
    while p * p <= n
        if sieve[i]
            append p to ps
            j := (p*p - 3) / 2
            while j < m
                sieve[j] := False
                j := j + p
        i := i + 1; p := p + 2
    while i < m
        if sieve[i]
            append p to ps
        i := i + 1; p := p + 2
    return ps

The strange formula for j in the original code is formed by taking the formula for j shown above and rewriting in terms of i instead of p.

If you're interested in programming with prime numbers, I have an essay at my blog that, among other things, discusses this exact formula in some depth.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top