Подтвердить что ты не робот

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

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

Введение

Я написал этот необычный маленький метод, который генерирует массив из 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;
}

Моя проблема

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

Фокус

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

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

Вопросы

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

Версия 2 (спасибо 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);
}

Версия 3 (спасибо Paul Tomblin), которая использует Сито 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;
}
4b9b3361

Ответ 1

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

Ответ 2

Создайте ArrayList<Integer>, а затем конвертируйте в int[] в конце.

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

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

Ответ 3

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

Ответ 4

Алго, использующее сито эратосфена

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

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

enter image description here

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

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

Ответ 9

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

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

Здесь код:

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

Ответ 10

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

Ответ 11

Я, наконец, закончил программу Это оптимизированное сито

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

Ответ 12

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

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

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