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é!
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.
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;
}
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é.
Voici comment le programme utilise les tableaux. C'est ce que je veux améliorer.
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?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;
}
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.
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;
}
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
.
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.)
Source de l'image: WikiMedia
La solution la plus simple serait de renvoyer un membre du Collections Framework au lieu d’un tableau.
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.
J'ai une implémentation vraiment efficace:
BitSet
, ne nécessitant qu'un bit par nombre.initialCapacity
pour le tableau de manière appropriée.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;
}
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").
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
.
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;
}
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
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.
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