Нахождение простых чисел с помощью Решета Эратосфена (Первоначально: есть ли лучший способ подготовить этот массив?)

Примечание: Версия 2, ниже, использует Сито Эратосфена. Есть несколько ответов, которые помогли с тем, что я изначально спросил. Я выбрал метод Сито Эратосфена, реализовал его и соответствующим образом изменил заголовок вопроса и теги. Спасибо всем, кто помогал!

Вступление

Я написал этот причудливый небольшой метод, который генерирует массив int, содержащий простые числа, меньшие указанной верхней границы. Он работает очень хорошо, но у меня есть опасения.

Метод

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;
}

Моя забота

Меня беспокоит то, что я создаю массив, который слишком велик для окончательного количества элементов, которые вернет метод. Проблема в том, что я не знаю хорошего способа правильно угадать количество простых чисел меньше заданного числа.

Фокус

Вот как программа использует массивы. Это то, что я хочу улучшить.

  1. Я создаю временный массив, достаточно большой, чтобы вместить каждое число меньше предела.
  2. Я генерирую простые числа, продолжая подсчитывать их количество.
  3. Я создаю новый массив с правильным размером, чтобы содержать только простые числа.
  4. Я копирую каждое простое число из огромного массива в массив правильной размерности.
  5. Я возвращаю массив правильного измерения, содержащий только сгенерированные мной простые числа.

Вопросов

  1. Могу ли я скопировать весь фрагмент (сразу) temp[], содержащий ненулевые элементы, primes[] без необходимости перебирать оба массива и копировать элементы один за другим?
  2. Существуют ли какие-либо структуры данных, которые ведут себя как массив примитивов, которые могут расти по мере добавления элементов, вместо того, чтобы требовать измерения при создании экземпляра? Какое снижение производительности по сравнению с использованием массива примитивов?

Версия 2 (спасибо Джону Скиту ):

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);
}

Версия 3 (спасибо Полу Томблину ), в которой используется Сито Эрастосфена :

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;
}

Ответов (13)

Решение

Ваш метод поиска простых чисел путем сравнения каждого отдельного элемента массива со всеми возможными факторами ужасающе неэффективен. Вы можете значительно улучшить его, выполнив Сито Эратосфена сразу по всему массиву. Помимо выполнения гораздо меньшего количества сравнений, он также использует сложение, а не деление. Разделение идет медленнее.

Самым простым решением было бы вернуть какой-то член Collections Framework вместо массива.

У меня действительно эффективная реализация:

  1. мы не храним четные числа, поэтому вдвое уменьшаем использование памяти.
  2. мы используем BitSet, требуя только один бит на число.
  3. мы оцениваем верхнюю границу количества простых чисел на интервале, таким образом, мы можем установить initialCapacityдля массива соответствующим образом.
  4. мы не выполняем никаких делений в петлях.

Вот код:

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;
}

Не уверен, что это подойдет вашей ситуации, но вы можете взглянуть на мой подход. Я использовал свое, используя Сито Эратосфена .

  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;
    }

Добавлен комментарий для каждого шага, проиллюстрированного в Википедии.

public static void primes(int n) {
        boolean[] lista = new boolean[n+1];
        for (int i=2;i<lista.length;i++) {
            if (lista[i]==false) {
                System.out.print(i + " ");
            }
            for (int j=i+i;j<lista.length;j+=i) {
                lista[j]=true;
            }
        }
    }

Алгоритм с использованием сита Эратосфена

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;
}

Изображение, изображающее вышеуказанный алгоритм (ячейки серого цвета представляют простое число. Поскольку мы изначально рассматриваем все числа как простые числа, вся сетка изначально является серой).

введите описание изображения здесь

Источник изображения: WikiMedia

Вы используете Java 1.5? Почему бы не вернуть List<Integer> и не использовать ArrayList<Integer> ? Если вам действительно нужно вернуть int[], вы можете сделать это, преобразовав List int[] в в конце обработки.

Теперь, когда у вас есть основное сито, обратите внимание, что внутренний цикл нужно продолжать только до temp[i]*temp[i] > prime .

В конце создайте, ArrayList<Integer> а затем преобразуйте int[] в.

Существуют различные сторонние IntList (и т. Д.) Классы, но я бы не стал беспокоиться об этом , если вы действительно не беспокоитесь о попадании в коробку нескольких целых чисел.

Однако вы можете использовать Arrays.copyOf для создания нового массива. Вы также можете изменять размер, удваивая размер каждый раз, когда вам нужно, а затем обрезать в конце. По сути, это имитирует ArrayList поведение.

Измените структуру кода. Выбросьте временный массив и вместо этого напишите функцию, которая просто проверяет целое число. Это будет достаточно быстро, поскольку вы используете только собственные типы. Затем вы можете, например, выполнить цикл и построить список простых целых чисел, прежде чем окончательно преобразовать его в возвращаемый массив.

Как указывает Пол Томблин, есть алгоритмы получше.

Но придерживаясь того, что у вас есть, и предполагая, что объект для каждого результата слишком велик:

Вы только когда-либо добавляете в массив. Итак, используйте относительно небольшой массив int []. Когда он будет полностью использован, добавьте его в список и создайте замену. В конце скопируйте его в массив правильного размера.

В качестве альтернативы угадывайте размер массива int []. Если он слишком мал, замените его на int [] с размером, на долю которого больше текущего размера массива. Накладные расходы на производительность останутся пропорциональными размеру. (Это кратко обсуждалось в недавнем подкасте stackoverflow.)

ArrayList<> Сито Эратосфена

// 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;
}

Формула для верхней границы числа простых чисел, меньших или равных max (см. Wolfram.com ):

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

Я использовал HashMap и нашел его очень простым

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);
                    }
                }

            }
        }

    }

}

Думаю, это эффективно