web-dev-qa-db-fra.com

Recherche de nombres premiers avec le tamis d'Eratosthène (à l'origine: existe-t-il un meilleur moyen de préparer ce tableau?)

Remarque: La version 2 ci-dessous utilise le tamis d'Eratosthène. Plusieurs réponses ont aidé avec ce que j'avais initialement demandé. J'ai choisi la méthode Sieve of Eratosthenes, je l'ai mise en œuvre et j'ai modifié le titre de la question et les balises de manière appropriée. Merci à tous ceux qui ont aidé!

Introduction

J'ai écrit cette petite méthode sophistiquée qui génère un tableau d'int contenant les nombres premiers inférieurs à la limite supérieure spécifiée. Cela fonctionne très bien, mais j'ai un souci.

La méthode

private static int [] generatePrimes(int max) {
    int [] temp = new int [max];
    temp [0] = 2;
    int index = 1;
    int prime = 1;
    boolean isPrime = false;
    while((prime += 2) <= max) {
        isPrime = true;
        for(int i = 0; i < index; i++) {
            if(prime % temp [i] == 0) {
                isPrime = false;
                break;
            }
        }
        if(isPrime) {
            temp [index++] = prime;
        }
    }
    int [] primes = new int [index];
    while(--index >= 0) {
        primes [index] = temp [index];
    }
    return primes;
}

Ma préoccupation

Mon souci est que je crée un tableau beaucoup trop grand pour le nombre final d'éléments retournés par la méthode. Le problème est que je ne connais pas un bon moyen de deviner correctement le nombre de nombres premiers inférieur à un nombre spécifié.

Concentrer

Voici comment le programme utilise les tableaux. C'est ce que je veux améliorer.

  1. Je crée un tableau temporaire suffisamment grand pour contenir chaque nombre inférieur à la limite.
  2. Je génère les nombres premiers tout en conservant Le nombre de ceux que j'ai générés.
  3. Je crée un nouveau tableau qui est la bonne dimension Pour contenir uniquement les premiers nombres .
  4. Je copie chaque nombre premier de l’énorme tableau Vers le tableau de la dimension correcte .
  5. Je retourne le tableau de la dimension Correcte contenant uniquement les premiers Nombres que j'ai générés.

Des questions

  1. Puis-je copier tout le morceau (à la fois) de temp[] qui contient des éléments Non nuls pour primes[] Sans avoir à parcourir les deux tableaux de Et à copier les éléments un par un?
  2. Existe-t-il des structures de données qui Se comportent comme un tableau de primitives Pouvant croître à mesure que des éléments sont ajoutés, Au lieu de nécessiter une dimension Lors de l'instanciation? Quelle est la pénalité de performance comparée à utilisant un tableau de primitives?

Version 2 (merci à Jon Skeet ):

private static int [] generatePrimes(int max) {
    int [] temp = new int [max];
    temp [0] = 2;
    int index = 1;
    int prime = 1;
    boolean isPrime = false;
    while((prime += 2) <= max) {
        isPrime = true;
        for(int i = 0; i < index; i++) {
            if(prime % temp [i] == 0) {
                isPrime = false;
                break;
            }
        }
        if(isPrime) {
            temp [index++] = prime;
        }
    }
    return Arrays.copyOfRange(temp, 0, index);
}

Version 3 (merci à Paul Tomblin ) qui utilise le tamis de Erastosthenes :

private static int [] generatePrimes(int max) {
    boolean[] isComposite = new boolean[max + 1];
    for (int i = 2; i * i <= max; i++) {
        if (!isComposite [i]) {
            for (int j = i; i * j <= max; j++) {
                isComposite [i*j] = true;
            }
        }
    }
    int numPrimes = 0;
    for (int i = 2; i <= max; i++) {
        if (!isComposite [i]) numPrimes++;
    }
    int [] primes = new int [numPrimes];
    int index = 0;
    for (int i = 2; i <= max; i++) {
        if (!isComposite [i]) primes [index++] = i;
    }
    return primes;
}
21
eleven81

Votre méthode de recherche de nombres premiers, en comparant chaque élément du tableau avec tous les facteurs possibles, est terriblement inefficace. Vous pouvez l’améliorer énormément en faisant un tamis d’Ératosthène sur tout le tableau à la fois. En plus de faire beaucoup moins de comparaisons, il utilise également l'addition plutôt que la division. La division est beaucoup plus lente.

13
Paul Tomblin

ArrayList<> Tamis d'Eratosthène

// Return primes less than limit
static ArrayList<Integer> generatePrimes(int limit) {
    final int numPrimes = countPrimesUpperBound(limit);
    ArrayList<Integer> primes = new ArrayList<Integer>(numPrimes);
    boolean [] isComposite    = new boolean [limit];   // all false
    final int sqrtLimit       = (int)Math.sqrt(limit); // floor
    for (int i = 2; i <= sqrtLimit; i++) {
        if (!isComposite [i]) {
            primes.add(i);
            for (int j = i*i; j < limit; j += i) // `j+=i` can overflow
                isComposite [j] = true;
        }
    }
    for (int i = sqrtLimit + 1; i < limit; i++)
        if (!isComposite [i])
            primes.add(i);
    return primes;
}

Formule pour la limite supérieure du nombre de nombres premiers inférieurs ou égaux à max (voir wolfram.com ):

static int countPrimesUpperBound(int max) {
    return max > 1 ? (int)(1.25506 * max / Math.log((double)max)) : 0;
}
9
jfs

Créez un ArrayList<Integer> puis convertissez-le en int[] à la fin.

Il existe différentes classes IntList (etc.) tierces, mais à moins que vous ne soyez vraiment inquiet à propos du succès de la boxe de quelques entiers, je ne m'en soucierais pas.

Cependant, vous pouvez utiliser Arrays.copyOf pour créer le nouveau tableau. Vous pouvez également vouloir redimensionner en doublant la taille à chaque fois que vous en avez besoin, puis couper à la fin. Ce serait fondamentalement imiter le comportement ArrayList.

8
Jon Skeet

Algo utilisant le tamis d'Eratosthenes

public static List<Integer> findPrimes(int limit) {

    List<Integer> list = new ArrayList<>();

    boolean [] isComposite = new boolean [limit + 1]; // limit + 1 because we won't use '0'th index of the array
    isComposite[1] = true;

    // Mark all composite numbers
    for (int i = 2; i <= limit; i++) {
        if (!isComposite[i]) {
            // 'i' is a prime number
            list.add(i);
            int multiple = 2;
            while (i * multiple <= limit) {
                isComposite [i * multiple] = true;
                multiple++;
            }
        }
    }

    return list;
}

Image illustrant l’algorithme ci-dessus (les cellules de couleur grise représentent un nombre premier. Puisque nous considérons tous les nombres comme des nombres premiers, la grille entière est grise au départ.)

enter image description here

Source de l'image: WikiMedia

7
Yatendra Goel

La solution la plus simple serait de renvoyer un membre du Collections Framework au lieu d’un tableau.

2
Hank Gay

Utilisez-vous Java 1.5? Pourquoi ne pas retourner List<Integer> et utiliser ArrayList<Integer>? Si vous devez renvoyer un int[], vous pouvez le faire en convertissant List en int[] à la fin du traitement.

2
Bogdan

J'ai une implémentation vraiment efficace:

  1. nous ne gardons pas les nombres pairs, donc divisant par deux l'utilisation de la mémoire.
  2. nous utilisons BitSet, ne nécessitant qu'un bit par nombre.
  3. nous estimons la limite supérieure du nombre de nombres premiers sur l’intervalle, nous pouvons donc définir la variable initialCapacity pour le tableau de manière appropriée.
  4. nous n'effectuons aucune sorte de division dans les boucles.

Voici le code:

public ArrayList<Integer> sieve(int n) {
    int upperBound = (int) (1.25506 * n / Math.log(n));
    ArrayList<Integer> result = new ArrayList<Integer>(upperBound);
    if (n >= 2)
        result.add(2);

    int size = (n - 1) / 2;
    BitSet bs = new BitSet(size);

    int i = 0;
    while (i < size) {
        int p = 3 + 2 * i;
        result.add(p);

        for (int j = i + p; j < size; j += p)
            bs.set(j);

        i = bs.nextClearBit(i + 1);
    }

    return result;
}
1
Rok Kralj

Comme le souligne Paul Tomblin, il existe de meilleurs algorithmes.

Mais en gardant ce que vous avez et en supposant qu'un objet par résultat est trop gros:

Vous ne faites qu'ajouter au tableau. Donc, utilisez un tableau int [] relativement petit. Quand il est utilisé, ajoutez-le à une liste et créez-en un remplaçant. À la fin, copiez-le dans un tableau correctement dimensionné.

Sinon, devinez la taille du tableau int []. S'il est trop petit, remplacez par un int [] de taille supérieure à la taille actuelle du tableau. Les frais généraux liés à la performance resteront proportionnels à la taille. (Cela a été discuté brièvement dans un podcast récent "stackoverflow").

1
Tom Hawtin - tackline

Maintenant que vous avez un tamis de base en place, notez que la boucle interne doit uniquement continuer jusqu'au temp[i]*temp[i] > prime

1
dmckee

J'ai finalement fini le programme C'est un tamis optimisé

public static int[] generatePrimes(int max) {
    boolean[] isComposite = new boolean[max + 1];
    LinkedList<Integer> Primes = new LinkedList<>(); //linkedlist so not have to iterate 2 times
    int sqrt = (int) Math.sqrt(max); //end at the square root
    for (int i = 2; i <= sqrt; i++) {
        if (!isComposite[i]) {
            int s = i*i; //start from the prime's square
            while (s <= max) {
                isComposite[s] = true; //oh no its a not prime
                s+=i;
            }
        }
    }
    for(int i = 2; i < max; i++){
        if(!isComposite[i]){
            Primes.add(i);
        }
    }
    int[] result = new int[Primes.size()];
    for (int i = 0; i < result.length; i++) {
        result[i] = Primes.get(i);
    }
    return result;
}
0
HiBrian

Je ne suis pas sûr que cela convienne à votre situation, mais vous pouvez jeter un coup d'œil à mon approche. J'ai utilisé le mien en utilisant Tamis d'Eratosthenes .

  public static List<Integer> sieves(int n) {
        Map<Integer,Boolean> numbers = new LinkedHashMap<>();

        List<Integer> primes = new ArrayList<>();

        //First generate a list of integers from 2 to 30
        for(int i=2; i<n;i++){
            numbers.put(i,true);
        }

        for(int i : numbers.keySet()){
            /**
             * The first number in the list is 2; cross out every 2nd number in the list after 2 by 
             * counting up from 2 in increments of 2 (these will be all the multiples of 2 in the list):
             * 
             * The next number in the list after 2 is 3; cross out every 3rd number in the list after 3 by 
             * counting up from 3 in increments of 3 (these will be all the multiples of 3 in the list):
             * The next number not yet crossed out in the list after 5 is 7; the next step would be to cross out every
             * 7th number in the list after 7, but they are all already crossed out at this point,
             * as these numbers (14, 21, 28) are also multiples of smaller primes because 7 × 7 is greater than 30. 
             * The numbers not crossed out at this point in the list are all the prime numbers below 30:
             */
            if(numbers.get(i)){
                for(int j = i+i; j<n; j+=i) {
                    numbers.put(j,false);
                }
            }
        }


        for(int i : numbers.keySet()){
            for(int j = i+i; j<n && numbers.get(i); j+=i) {
                numbers.put(j,false);
            }
        }

        for(int i : numbers.keySet()){
           if(numbers.get(i)) {
               primes.add(i);
           }
        }
        return primes;
    }

Ajout de commentaire pour chaque étape illustrée dans wikipedia

0
KyelJmD

Restructurez votre code. Jetez le tableau temporaire et écrivez à la place une fonction qui teste simplement un entier. Ce sera assez rapide, car vous utilisez uniquement des types natifs. Ensuite, vous pouvez, par exemple, boucler et construire une liste d’entiers premiers, avant de convertir celle-ci en un tableau à renvoyer.

0
unwind

J'ai utilisé HashMap et je l'ai trouvé très simple 

import Java.util.HashMap;
import Java.util.Map;

/*Using Algorithms such as sieve of Eratosthanas */

public class PrimeNumber {

    public static void main(String[] args) {

        int prime = 15;
        HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();

        hashMap.put(0, 0);
        hashMap.put(1, 0);
        for (int i = 2; i <= prime; i++) {

            hashMap.put(i, 1);// Assuming all numbers are prime
        }

        printPrimeNumberEratoshanas(hashMap, prime);

    }

    private static void printPrimeNumberEratoshanas(HashMap<Integer, Integer> hashMap, int prime) {

        System.out.println("Printing prime numbers upto" + prime + ".....");
        for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) {
            if (entry.getValue().equals(1)) {
                System.out.println(entry.getKey());
                for (int j = entry.getKey(); j < prime; j++) {
                    for (int k = j; k * j <= prime; k++) {
                        hashMap.put(j * k, 0);
                    }
                }

            }
        }

    }

}

Pense que c'est efficace

0
Sameer Desai