web-dev-qa-db-fra.com

Tamis segmenté d'Eratosthenes?

Il est assez facile de faire un tamis simple:

for (int i=2; i<=N; i++){
    if (sieve[i]==0){
        cout << i << " is prime" << endl;
        for (int j = i; j<=N; j+=i){
            sieve[j]=1;
        }
    }
    cout << i << " has " << sieve[i] << " distinct prime factors\n";
}

Mais qu'en est-il lorsque N est très grand et que je ne peux pas garder ce type de tableau en mémoire? J'ai examiné les méthodes de tamisage segmentées et elles semblent impliquer la recherche de nombres premiers jusqu'au carré (N) mais je ne comprends pas comment cela fonctionne. Et si N est très grand (disons 10 ^ 18)?

34
John Smith

L’idée de base d’un tamis segmenté est de choisir les nombres premiers de tamisage inférieurs à la racine carrée de n, de choisir une taille de segment raisonnablement grande qui tient néanmoins dans la mémoire, puis de tamiser tour à tour chacun des segments, en commençant par avec le plus petit. Au premier segment, le plus petit multiple de chaque nombre premier de tamisage qui se trouve dans le segment est calculé, puis les multiples du nombre premier de tamisage sont marqués comme composites de manière normale; lorsque tous les nombres premiers de tamisage ont été utilisés, les nombres non marqués restants dans le segment sont premiers. Ensuite, pour le segment suivant, vous connaissez déjà le premier multiple du segment en cours (c’est le multiple qui a mis fin au tamisage pour le nombre précédent dans le segment précédent); vous passez donc au tamis pour chaque premier tamisage, etc. jusqu'à ce que tu aies fini.

La taille de n n'a pas d'importance, sauf qu'un plus grand n prendra plus de temps à tamiser qu'un plus petit n; la taille qui compte est la taille du segment, qui doit être aussi grande que convenable (par exemple, la taille du cache de la mémoire principale sur la machine).

Vous pouvez voir une simple implémentation d'un tamis segmenté ici . Notez qu'un tamis segmenté sera beaucoup plus rapide que le tamis à priorité de O'Neill mentionné dans une autre réponse; si cela vous intéresse, il y a une implémentation ici .

EDIT: J'ai écrit ceci dans un but différent, mais je vais le montrer ici car cela pourrait être utile:

Bien que le crible d'Ératosthène soit très rapide, il nécessite O(n) d’espace. Cela peut être réduit à O(sqrt(n)) pour les nombres premiers de tamisage plus O(1) pour le réseau de bits en effectuant le tamisage par segments successifs. Au premier segment, le plus petit multiple de chaque nombre premier de tamisage qui se trouve dans le segment est calculé, puis les multiples du nombre premier de tamisage sont marqués comme étant composites de manière normale; lorsque tous les nombres premiers de tamisage ont été utilisés, les nombres non marqués restants dans le segment sont premiers. Ensuite, pour le segment suivant, le plus petit multiple de chaque prime de tamisage est le multiple qui a mis fin au tamisage dans le segment précédent et le tamisage se poursuit ainsi jusqu'à la fin.

Prenons l'exemple du tamis de 100 à 200 en segments de 20. Les cinq nombres premiers du tamisage sont 3, 5, 7, 11 et 13. Dans le premier segment de 100 à 120, la matrice de bits comporte dix créneaux, le créneau 0 correspondant à 101 , le créneau k correspondant à 100 + 2k + 1 et le créneau 9 correspondant à 119. Le plus petit multiple de 3 du segment est 105, ce qui correspond au créneau 2; les intervalles 2 + 3 = 5 et 5 + 3 = 8 sont également des multiples de 3. Le plus petit multiple de 5 est 105 à l'emplacement 2, et l'intervalle 2 + 5 = 7 est également un multiple de 5. Le plus petit multiple de 7 est 105 aux emplacements 2 et 2 + 7 = 9 correspond également à un multiple de 7. Et ainsi de suite.

Fonction primesRange prend les arguments lo, hi and delta; lo et hi doivent être égaux, avec lo <hi, et lo doivent être supérieurs à sqrt (hi). La taille du segment est deux fois delta. Ps est une liste chaînée contenant les nombres premiers de tamisage inférieurs à sqrt (hi), 2 étant supprimés car les nombres pairs sont ignorés. Qs est une liste chaînée contenant l’offest dans le tamis bitarray du plus petit multiple du segment en cours du nombre de tamisage correspondant. Après chaque segment, lo avance de deux fois delta, de sorte que le nombre correspondant à un indice i du tamis bitarray est lo + 2i + 1.

function primesRange(lo, hi, delta)
    function qInit(p)
        return (-1/2 * (lo + p + 1)) % p
    function qReset(p, q)
        return (q - delta) % p
    sieve := makeArray(0..delta-1)
    ps := tail(primes(sqrt(hi)))
    qs := map(qInit, ps)
    while lo < hi
        for i from 0 to delta-1
            sieve[i] := True
        for p,q in ps,qs
            for i from q to delta step p
                sieve[i] := False
        qs := map(qReset, ps, qs)
        for i,t from 0,lo+1 to delta-1,hi step 1,2
            if sieve[i]
                output t
        lo := lo + 2 * delta

Lorsqu'ils sont appelés primesRange (100, 200, 10), les nombres premiers ps de tamisage sont [3, 5, 7, 11, 13]; qs est initialement [2, 2, 2, 10, 8] correspondant aux plus petits multiples 105, 105, 105, 121 et 117 et est réinitialisé pour le deuxième segment sur [1, 2, 6, 0, 11] correspondant aux plus petits multiples 123, 125, 133, 121 et 143.

Vous pouvez voir ce programme en action sur  http://ideone.com/iHYr1f. Et en plus des liens indiqués ci-dessus, si vous êtes intéressé par la programmation avec des nombres premiers, je le recommande modestement essai sur mon blog.

48
user448810

Il existe une version du tamis basée sur les files d'attente de priorité qui génère autant de nombres premiers que vous le souhaitez, plutôt que tous jusqu'à une limite supérieure. Il est discuté dans le papier classique "Le tamis authentique d'Eratosthenes" et googler pour "tamis de file d'attente prioritaire d'eratosthenes" donne plusieurs implémentations dans différents langages de programmation. 

4
Fred Foo

Nous faisons simplement une segmentation avec le tamis que nous avons ... L'idée de base est que nous devons trouver des nombres premiers compris entre 85 et 100 ... Nous devons appliquer le tamis traditionnel, mais de la manière suivante: décrit ci-dessous:

Donc nous prenons le premier nombre premier 2, divisons le nombre de départ par 2(85/2) et en arrondissant à un nombre inférieur, nous obtenons p = 42, maintenant multiplions à nouveau par 2 nous obtenons p = 84, à partir de maintenant continuez en ajoutant 2 jusqu'au dernier chiffre. Nous avons donc supprimé tous les facteurs de 2 (86,88,90,92,94,96,98,100) de la plage.

On prend le prochain nombre premier 3, on divise le nombre de départ par 3(85/3) et en arrondissant un nombre plus petit on obtient p = 28, on multiplie maintenant par 3 et on obtient p = 84, à partir de maintenant Commencez par ajouter 3 jusqu'au dernier chiffre. Nous avons donc supprimé tous les facteurs de 3 (87,90,93,96,99) de la plage.

Prenez le prochain nombre premier = 5 et ainsi de suite .................. Continuez à suivre les étapes ci-dessus.Vous pouvez obtenir les nombres premiers (2,3,5 , 7, ...) en utilisant le tamis traditionnel jusqu’à sqrt (n) .Et utilisez-le ensuite pour tamis segmenté. 

3
OneWhoKnocks

Si quelqu'un souhaite voir l'implémentation C++, voici le mien:

void sito_delta( int delta, std::vector<int> &res)
{

std::unique_ptr<int[]> results(new int[delta+1]);
for(int i = 0; i <= delta; ++i)
    results[i] = 1;

int pierw = sqrt(delta);
for (int j = 2; j <= pierw; ++j)
{
    if(results[j])
    {
        for (int k = 2*j; k <= delta; k+=j)
        {
            results[k]=0;
        }
    }
}

for (int m = 2; m <= delta; ++m)
    if (results[m])
    {
        res.Push_back(m);
        std::cout<<","<<m;
    }
};
void sito_segment(int n,std::vector<int> &fiPri)
{
int delta = sqrt(n);

if (delta>10)
{
    sito_segment(delta,fiPri);
   // COmpute using fiPri as primes
   // n=n,prime = fiPri;
      std::vector<int> prime=fiPri;
      int offset = delta;
      int low = offset;
      int high = offset * 2;
      while (low < n)
      {
          if (high >=n ) high = n;
          int mark[offset+1];
          for (int s=0;s<=offset;++s)
              mark[s]=1;

          for(int j = 0; j< prime.size(); ++j)
          {
            int lowMinimum = (low/prime[j]) * prime[j];
            if(lowMinimum < low)
                lowMinimum += prime[j];

            for(int k = lowMinimum; k<=high;k+=prime[j])
                mark[k-low]=0;
          }

          for(int i = low; i <= high; i++)
              if(mark[i-low])
              {
                fiPri.Push_back(i);
                std::cout<<","<<i;
              }
          low=low+offset;
          high=high+offset;
      }
}
else
{

std::vector<int> prime;
sito_delta(delta, prime);
//
fiPri = prime;
//
int offset = delta;
int low = offset;
int high = offset * 2;
// Process segments one by one 
while (low < n)
{
    if (high >= n) high = n;
    int  mark[offset+1];
    for (int s = 0; s <= offset; ++s)
        mark[s] = 1;

    for (int j = 0; j < prime.size(); ++j)
    {
        // find the minimum number in [low..high] that is
        // multiple of prime[i] (divisible by prime[j])
        int lowMinimum = (low/prime[j]) * prime[j];
        if(lowMinimum < low)
            lowMinimum += prime[j];

        //Mark multiples of prime[i] in [low..high]
        for (int k = lowMinimum; k <= high; k+=prime[j])
            mark[k-low] = 0;
    }

    for (int i = low; i <= high; i++)
        if(mark[i-low])
        {
            fiPri.Push_back(i);
            std::cout<<","<<i;
        }
    low = low + offset;
    high = high + offset;
}
}
};

int main()
{
std::vector<int> fiPri;
sito_segment(1013,fiPri);
}
0
Tomasz Andel

Basé sur la réponse de Swapnil Kumar, j’ai utilisé l’algorithme suivant en C. Il a été construit avec mingw32-make.exe.

#include<math.h>
#include<stdio.h>
#include<stdlib.h>

int main()
{
    const int MAX_PRIME_NUMBERS = 5000000;//The number of prime numbers we are looking for
    long long *prime_numbers = malloc(sizeof(long long) * MAX_PRIME_NUMBERS);
    prime_numbers[0] = 2;
    prime_numbers[1] = 3;
    prime_numbers[2] = 5;
    prime_numbers[3] = 7;
    prime_numbers[4] = 11;
    prime_numbers[5] = 13;
    prime_numbers[6] = 17;
    prime_numbers[7] = 19;
    prime_numbers[8] = 23;
    prime_numbers[9] = 29;
    const int BUFFER_POSSIBLE_PRIMES = 29 * 29;//Because the greatest prime number we have is 29 in the 10th position so I started with a block of 841 numbers
    int qt_calculated_primes = 10;//10 because we initialized the array with the ten first primes
    int possible_primes[BUFFER_POSSIBLE_PRIMES];//Will store the booleans to check valid primes
    long long iteration = 0;//Used as multiplier to the range of the buffer possible_primes
    int i;//Simple counter for loops
    while(qt_calculated_primes < MAX_PRIME_NUMBERS)
    {
        for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
            possible_primes[i] = 1;//set the number as prime

        int biggest_possible_prime = sqrt((iteration + 1) * BUFFER_POSSIBLE_PRIMES);

        int k = 0;

        long long prime = prime_numbers[k];//First prime to be used in the check

        while (prime <= biggest_possible_prime)//We don't need to check primes bigger than the square root
        {
            for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
                if ((iteration * BUFFER_POSSIBLE_PRIMES + i) % prime == 0)
                    possible_primes[i] = 0;

            if (++k == qt_calculated_primes)
                break;

            prime = prime_numbers[k];
        }
        for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
            if (possible_primes[i])
            {
                if ((qt_calculated_primes < MAX_PRIME_NUMBERS) && ((iteration * BUFFER_POSSIBLE_PRIMES + i) != 1))
                {
                    prime_numbers[qt_calculated_primes] = iteration * BUFFER_POSSIBLE_PRIMES + i;
                    printf("%d\n", prime_numbers[qt_calculated_primes]);
                    qt_calculated_primes++;
                } else if (!(qt_calculated_primes < MAX_PRIME_NUMBERS))
                    break;
            }

        iteration++;
    }

    return 0;
}

Il définit un maximum de nombres premiers à rechercher, puis un tableau contenant des nombres premiers connus, tels que 2, 3, 5 ... 29, est initialisé. Nous fabriquons donc un tampon qui stockera les segments de nombres premiers possibles. Ce tampon ne peut pas être supérieur à la puissance du plus grand nombre initial initial, qui dans ce cas est 29.

Je suis certain que de nombreuses optimisations peuvent être effectuées pour améliorer les performances, telles que la parallélisation du processus d’analyse des segments et la suppression des nombres multiples de 2, 3 et 5, mais cela constitue un exemple de faible consommation de mémoire.

0
Bruno Simas Hadlich