Вывод списка простых чисел в кратчайшие сроки

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

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

следующий мой код, могу ли я иметь лучшая версия того же самого?

 public static void main(String[] args) {
    for (int i = 2; i < 100000; i++) {
        if (checkMod(i)) {
            primes.add(i);
        }
    }
}

private static boolean checkMod( int num) {
    for (int i : primes){
        if( num % i == 0){
            return false;
        }
    }
    return true;
}

5 ответов


хорошая вещь в вашем тесте на примитивность заключается в том, что вы делите только на простые числа.

private static boolean checkMod( int num) {
    for (int i : primes){
        if( num % i == 0){
            return false;
        }
    }
    return true;
}

плохо то, что вы делите на все простые числа найдены до сих пор, то есть все простые числа меньше кандидата. Это означает, что для самого большого простого числа ниже миллиона, 999983, вы делите на 78497 простых чисел, чтобы узнать, что это число является простым. Это большая работа. Так много, на самом деле, что работа, потраченная на простые числа в этом алгоритме, составляет около 99,9% всей работы при переходе на миллион, большая часть для более высоких пределов. И этот алгоритм почти квадратичен, чтобы найти простые числа до n таким образом, вам нужно выполнить около

n² / (2*(log n)²)

подразделения.

простое улучшение на остановку раньше отдел. Пусть n быть составным числом (т. е. числом greter чем 1, которое имеет делители, отличные от 1 и n), и пусть d делитель n.

теперь d будучи делитель n значит, что n/d является целым числом, а также делителем n: n/(n/d) = d. Таким образом, мы можем естественно сгруппировать делители n на пары, каждый делитель d порождает пары (d, n/d).

для такой пары, есть две возможности:

  1. d = n/d, что означает n = d² или d = √n.
  2. они разные, тогда один из них меньше другого, скажем d < n/d. Но это сразу же переводится как d² < n или d < √n.

так или иначе, каждая пара делителей содержит (по крайней мере) один, не превышающий √n, следовательно, если n является составным числом, его наименьший делитель (кроме 1) не превышает √n.

таким образом, мы можем остановить судебный отдел, когда мы достигли √n:

private static boolean checkMod( int num) {
    for (int i : primes){
        if (i*i > n){
            // We have not found a divisor less than √n, so it's a prime
            return true;
        }
        if( num % i == 0){
            return false;
        }
    }
    return true;
}

Примечание: это зависит от списка простых чисел, которые повторяются в порядке возрастания. Если это не гарантируется языком, вы должны использовать другой метод, итерация по индексу через ArrayList или что-то в этом роде.

остановив пробное деление в квадратном корне кандидата, для самого большого простого числа ниже миллиона, 999983, нам теперь нужно только разделить его на 168 простых чисел ниже 1000. Это намного меньше работы, чем раньше. Остановка пробного деления в квадратном корне и деление только на простые числа-это все, что может получить пробное деление и требует около

2*n^1.5 / (3*(log n)²)

подразделения, для n = 1000000, это коэффициент около 750, неплохо, не так ли?

но это все еще не очень эффективно, наиболее эффективные методы для поиска всех простых чисел ниже n сит. Простой в реализации является классическим решето Эратосфена. Что находит простые числа ниже n в o (n*log log n) операциях, с некоторыми улучшениями(устраняя кратные нескольких малых простых чисел из рассмотрения заранее), его сложность может быть уменьшена до o (n) операций. Относительно новое сито с лучшим асимптотическим поведением является решето Аткина, который находит простые числа n в o(n) операциях или с улучшением устранения кратных некоторых малых простых чисел в o (n/log log n) операциях. Сито Аткина сложнее в реализации, поэтому вполне вероятно, что хорошая реализация сита Эратосфена выполняет лучше, чем наивная реализация сита Аткина. Для реализаций подобных уровней оптимизации производительность разница небольшая, если предел не станет большим (больше 1010; и это не редкость, что на практике сито Эратосфена масштабируется лучше, чем сито Аткина за пределами этого, из-за лучших шаблонов доступа к памяти). Поэтому я бы рекомендовал начать с сита Эратосфена, и только когда его производительность неудовлетворительна, несмотря на честные усилия по оптимизации, углубиться в сито Аткина. Или, если вы не хотите реализовать себя, найти хорошую реализацию кто-то уже серьезно настроился.

я пошел в немного более подробно ответ с немного другой настройкой, где проблема заключалась в поиске n-го простого числа. Некоторые реализации более или менее эффективных методов связаны с этим ответом, в частности одна или две полезные (хотя и не очень оптимизированные) реализации сита Эратосфена.


Я всегда использую сито Эратосфена:

isPrime[100001] // - initially contains only '1' values (1,1,1 ... 1)
isPrime[0] = isPrime[1] = 0 // 0 and 1 are not prime numbers

primes.push(2); //first prime number. 2 is a special prime number because is the only even prime number.
for (i = 2; i * 2 <= 100000; i++) isPrime[i * 2] = 0 // remove all multiples of 2

for (i = 3; i <= 100000; i += 2) // check all odd numbers from 2 to 100000
    if (isPrime[i]) {
        primes.push(i); // add the new prime number to the solution
        for (j = 2; i * j <= 100000; j++) isPrime[i * j] = 0; // remove all i's multiples
    }

return primes

Я надеюсь, вы понимаете мои комментарии


Я понимаю, что простое число - это число, которое делится только на себя и число 1 (без остатка). См.Статья В Википедии

это, как говорится, я не очень хорошо понимаю алгоритм во втором комментарии, но одним небольшим улучшением вашего алгоритма было бы изменить цикл for на:

for (int i = 5; i < 100000; i = i + 2) {
    if (checkMod(i)) {
        primes.add(i);
    }
}

это основано на предположении, что 1, 2 и 3 являются простыми числами, и все четные числа после этого не являются простыми числами. Это, по крайней мере, сокращает ваш алгоритм наполовину.


Я хочу сделать еще немного улучшенную версию для 0ne, предложенной Бенджамином Оман выше, Это только одна модификация, чтобы избежать проверки на примитивность всех чисел, заканчивающихся цифрой "5", потому что эти числа, конечно, не простые числа, поскольку они делятся на 5.

for (int i = 7;(i < 100000) && (!i%5==0); i = i + 2) {
    if (checkMod(i)) {
        primes.add(i);
    }
}

это основано на предположении, что 2,3,5 являются простыми. Вышеуказанное небольшое изменение уменьшит все факторы 5 и улучшит.


хорошо объяснили @Daniel Fischer.

реализация на C++ из его объяснения:

#include<iostream>

using namespace std;

long* getListOfPrimeNumbers (long total)
{
  long * primes;
  primes = new long[total];
  int count = 1;
  primes[0] = 2;
  primes[1] = 3;
  while (count < total)
  {
    long composite_number = primes[count] + 2;
    bool is_prime = false;
    while (is_prime == false)
    {
      is_prime = true;
      for (int i = 0; i <= count; i++)
      {
        long prime = primes[i];
        if (prime * prime > composite_number)
        {
          break;
        }
        if (composite_number % prime == 0)
        {
          is_prime = false;
          break;
      }
      }
      if (is_prime == true)
      {
        count++;
        primes[count] = composite_number;
      }
      else
      {
        composite_number += 2;
    }
  }
  }
  return primes;
}

int main()
{
  long * primes;
  int total = 10;
  primes = getListOfPrimeNumbers(total);
  for (int i = 0; i < total; i++){
    cout << primes[i] << "\n";
  }
  return 0;
}