Самый эффективный код для первых 10000 простых чисел?

Я хочу напечатать первые 10000 простых чисел. Может ли кто-нибудь дать мне наиболее эффективный код для этого? Разъяснения:

  1. Не имеет значения, если ваш код неэффективен для n> 10000.
  2. Размер кода значения не имеет.
  3. Вы не можете просто жестко закодировать значения каким-либо образом.

Ответов (25)

Решение

Сито Аткина , вероятно, то, что вы ищете, его верхняя граница времени работы - O (N / log log N).

Если вы запустите только числа на 1 больше и 1 меньше, чем числа, кратные 6, это может быть даже быстрее, поскольку все простые числа выше 3 находятся на расстоянии 1 от некоторого числа, кратного шести. Ресурс для моего заявления

Я рекомендую сито: Сито Эратосфена или Сито Аткина.

Сито или Эратосфен, вероятно, является наиболее интуитивным методом поиска списка простых чисел. В основном вы:

  1. Запишите список чисел от 2 до любого предела, который вы хотите, скажем, 1000.
  2. Возьмите первое число, которое не вычеркнуто (для первой итерации это 2), и вычеркните все кратные этого числа из списка.
  3. Повторяйте шаг 2, пока не дойдете до конца списка. Все числа, которые не вычеркнуты, простые.

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

Решето Аткина использует аналогичный подход, но, к сожалению, я недостаточно знаю о нем, чтобы вам это объяснить. Но я знаю, что связанному мной алгоритму требуется 8 секунд, чтобы вычислить все простые числа до 1000000000 на древнем Pentium II-350.

Сито Эратосфена Исходный код: http://web.archive.org/web/20140705111241/http://primes.utm.edu/links/programs/sieves/Eratosthenes/C_source_code/

Сито Исходного кода Аткина: http://cr.yp.to/primegen.html

@Matt: log (log (10000)) составляет ~ 2

Из статьи в Википедии (которую вы цитировали) Сито Аткина :

Это решето вычисляет простые числа до N, используя O(N/log log N)операции только с N 1/2 + o (1) битами памяти. Это немного лучше, чем сито Эратосфена, которое использует O(N) операции и O (N 1/2 (log log N) / log N) бит памяти (AOL Atkin, DJ Bernstein, 2004) . Эти асимптотические вычислительные сложности включают простые оптимизации, такие как факторизация колеса и разделение вычислений на более мелкие блоки.

Учитывая асимптотические вычислительные сложности O(N) (для Эратосфена) и O(N/log(log(N))) (для Аткина), мы не можем сказать (для малых N=10_000 ), какой алгоритм, если он будет реализован, будет быстрее.

Ахим Фламменкамп писал в « Решете Эратосфена» :

цитируется:

@ num1

Для интервалов, больших примерно 10 ^ 9, конечно, для тех, кто> 10 ^ 10, Решето Эратосфена превосходит Решето Аткинса и Бернштейна, которое использует неприводимые двоичные квадратичные формы. См. Их статью для получения справочной информации, а также параграф 5 докторской диссертации У. Голуэя. Тезис.

Следовательно, 10_000 Сито Эратосфена может быть быстрее, чем Сито Аткина.

Чтобы ответить OP, код: prime_sieve.c (цитируется num1 )

В Python

import gmpy
p=1
for i in range(10000):
    p=gmpy.next_prime(p)
    print p 

Я трачу некоторое время на написание программы, вычисляющей множество простых чисел, и это код, который я использую для вычисления текстового файла, содержащего первые 1.000.000.000 простых чисел. Это на немецком языке, но самое интересное - это метод calcPrimes() . Простые числа хранятся в массиве под названием Primzahlen. Я рекомендую 64-битный процессор, потому что вычисления производятся с 64-битными целыми числами.

import java.io.*;
class Primzahlengenerator {
    long[] Primzahlen;
    int LastUnknown = 2;
    public static void main(String[] args)  {
        Primzahlengenerator Generator = new Primzahlengenerator();
        switch(args.length) {
            case 0:  //Wenn keine Argumente übergeben worden:
                Generator.printHelp(); //Hilfe ausgeben
                return; //Durchfallen verhindern
            case 1:
                try {
                    Generator.Primzahlen = new long[Integer.decode(args[0]).intValue()];
                }
                catch (NumberFormatException e) {
                    System.out.println("Das erste Argument muss eine Zahl sein, und nicht als Wort z.B. \"Tausend\", sondern in Ziffern z.B. \"1000\" ausgedrückt werden.");//Hinweis, wie man die Argumente angeben muss ausgeben
                    Generator.printHelp();                    //Generelle Hilfe ausgeben
                    return;
                }
                break;//dutchfallen verhindern

            case 2:
                switch (args[1]) {
                    case "-l":
                        System.out.println("Sie müsen auch eine Datei angeben!"); //Hilfemitteilung ausgeben
                        Generator.printHelp();                                    //Generelle Hilfe ausgeben
                        return;
                }
                break;//durchfallen verhindern
            case 3:
                try {
                    Generator.Primzahlen = new long[Integer.decode(args[0]).intValue()];
                }
                catch (NumberFormatException e) {
                    System.out.println("Das erste Argument muss eine Zahl sein, und nicht als Wort z.B. \"Tausend\", sondern in Ziffern z.B. \"1000\" ausgedrückt werden.");//Hinweis, wie man die Argumente angeben muss ausgeben
                    Generator.printHelp();                      //Generelle Hilfe ausgeben
                    return;
                }
                switch(args[1]) {
                    case "-l":
                        Generator.loadFromFile(args[2]);//Datei Namens des Inhalts von Argument 3 lesen, falls Argument 2 = "-l" ist
                        break;
                    default:
                        Generator.printHelp();
                        break;
                }
                break;
            default:
                Generator.printHelp();
                return;
        }
        Generator.calcPrims();
    }
    void printHelp() {
        System.out.println("Sie müssen als erstes Argument angeben, die wieviel ersten Primzahlen sie berechnen wollen.");   //Anleitung wie man das Programm mit Argumenten füttern muss
        System.out.println("Als zweites Argument können sie \"-l\" wählen, worauf die Datei, aus der die Primzahlen geladen werden sollen,");
        System.out.println("folgen muss. Sie muss genauso aufgebaut sein, wie eine Datei Primzahlen.txt, die durch den Aufruf \"java Primzahlengenerator 1000 > Primzahlen.txt\" entsteht.");
    }
    void loadFromFile(String File) {
        // System.out.println("Lese Datei namens: \"" + File + "\"");
        try{
            int x = 0;
            BufferedReader in = new BufferedReader(new FileReader(File));
            String line;
            while((line = in.readLine()) != null) {
                Primzahlen[x] = new Long(line).longValue();
                x++;
            }
            LastUnknown = x;
        } catch(FileNotFoundException ex) {
            System.out.println("Die angegebene Datei existiert nicht. Bitte geben sie eine existierende Datei an.");
        } catch(IOException ex) {
            System.err.println(ex);
        } catch(ArrayIndexOutOfBoundsException ex) {
            System.out.println("Die Datei enthält mehr Primzahlen als der reservierte Speicherbereich aufnehmen kann. Bitte geben sie als erstes Argument eine größere Zahl an,");
            System.out.println("damit alle in der Datei enthaltenen Primzahlen aufgenommen werden können.");
            }
        /* for(long prim : Primzahlen) {
            System.out.println("" + prim);
        } */
        //Hier soll code stehen, der von der Datei mit angegebenem Namen ( Wie diese aussieht einfach durch angeben von folgendem in cmd rausfinden:
        //java Primzahlengenerator 1000 > 1000Primzahlen.txt
        //da kommt ne textdatei, die die primzahlen enthält. mit Long.decode(String ziffern).longValue();
        //erhält man das was an der entsprechenden stelle in das array soll. die erste zeile soll in [0] , die zweite zeile in [1] und so weiter.
        //falls im arry der platz aus geht(die exception kenn ich grad nich, aber mach mal:
        //int[] foo = { 1, 2, 3};
        //int bar = foo[4];
        //dann kriegst ne exception, das ist die gleiche die man kriegt, wenn im arry der platzt aus geht.
    }
    void calcPrims() {
        int PrimzahlNummer = LastUnknown;
        // System.out.println("LAstUnknown ist: " + LastUnknown);
        Primzahlen[0] = 2;
        Primzahlen[1] = 3;
        long AktuelleZahl = Primzahlen[PrimzahlNummer - 1];
        boolean IstPrimzahl;
        // System.out.println("2");
        // System.out.println("3");
        int Limit = Primzahlen.length;
        while(PrimzahlNummer < Limit) {
            IstPrimzahl = true;
            double WurzelDerAktuellenZahl = java.lang.Math.sqrt(AktuelleZahl);
            for(int i = 1;i < PrimzahlNummer;i++) {
                if(AktuelleZahl % Primzahlen[i] == 0) {
                    IstPrimzahl = false;
                    break;
                }
                if(Primzahlen[i] > WurzelDerAktuellenZahl) break;
            }
            if(IstPrimzahl) {
                Primzahlen[PrimzahlNummer] = AktuelleZahl;
                PrimzahlNummer++;
                // System.out.println("" + AktuelleZahl);
            }
            AktuelleZahl = AktuelleZahl + 2;
        }
        for(long prim : Primzahlen) {
            System.out.println("" + prim);
        }
    }
}

Вот мой код, который находит первые 10 000 простых чисел за 0,049655 секунды на моем ноутбуке, первые 1 000 000 простых чисел менее чем за 6 секунд и первые 2 000 000 простых чисел за 15 секунд.
Небольшое объяснение. В этом методе используются 2 метода нахождения простых чисел.

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

Пример вывода первых 10 000 простых чисел
https://drive.google.com/open?id=0B2QYXBiLI-lZMUpCNFhZeUphck0 https://drive.google.com/open?id=0B2QYXBiLI-lZbmRtTkZETnp6Ykk

Вот код на языке C: введите 1, а затем 10 000, чтобы распечатать первые 10 000 простых чисел.
Изменить: я забыл, что это содержит математическую библиотеку, если вы работаете в Windows или Visual Studio, это должно быть хорошо, но в Linux вы должны скомпилировать код с использованием аргумента -lm, иначе код может не работать
Пример: gcc -Wall -o "% e ""% f "-lm

#include <stdio.h>
#include <math.h>
#include <time.h>
#include <limits.h>

/* Finding prime numbers */
int main()
{   
    //pre-phase
    char d,w;
    int l,o;
    printf("  1. Find first n number of prime numbers or Find all prime numbers smaller than n ?\n"); // this question helps in setting the limits on m or n value i.e l or o 
    printf("     Enter 1 or 2 to get anwser of first or second question\n");
    // decision making
    do
    {
        printf("  -->");
        scanf("%c",&d);
        while ((w=getchar()) != '\n' && w != EOF);
        if ( d == '1')
        {
            printf("\n  2. Enter the target no. of primes you will like to find from 3 to 2,000,000 range\n  -->");
            scanf("%10d",&l);
            o=INT_MAX;
            printf("  Here we go!\n\n");
            break;
        }
        else if ( d == '2' )
        {
            printf("\n  2.Enter the limit under which to find prime numbers from 5 to 2,000,000 range\n  -->");
            scanf("%10d",&o);
            l=o/log(o)*1.25;
            printf("  Here we go!\n\n");
            break;
        }
        else printf("\n  Try again\n");
    }while ( d != '1' || d != '2' );

    clock_t start, end;
    double cpu_time_used;
    start = clock(); /* starting the clock for time keeping */

    // main program starts here
    int i,j,c,m,n; /* i ,j , c and m are all prime array 'p' variables and n is the number that is being tested */
    int s,x;

    int p[ l ]; /* p is the array for storing prime numbers and l sets the array size, l was initialized in pre-phase */
    p[1]=2;
    p[2]=3;
    p[3]=5;
    printf("%10dst:%10d\n%10dnd:%10d\n%10drd:%10d\n",1,p[1],2,p[2],3,p[3]); // first three prime are set
    for ( i=4;i<=l;++i ) /* this loop sets all the prime numbers greater than 5 in the p array to 0 */
        p[i]=0;

    n=6; /* prime number testing begins with number 6 but this can lowered if you wish but you must remember to update other variables too */
    s=sqrt(n); /* 's' does two things it stores the root value so that program does not have to calaculate it again and again and also it stores it in integer form instead of float*/
    x=2; /* 'x' is the biggest prime number that is smaller or equal to root of the number 'n' being tested */

    /* j ,x and c are related in this way, p[j] <= prime number x <= p[c] */

    // the main loop begins here
    for ( m=4,j=1,c=2; m<=l && n <= o;)
    /* this condition checks if all the first 'l' numbers of primes are found or n does not exceed the set limit o */
    {
            // this will divide n by prime number in p[j] and tries to rule out non-primes
            if ( n%p[j]==0 )
            {
                /* these steps execute if the number n is found to be non-prime */

                ++n; /* this increases n by 1 and therefore sets the next number 'n' to be tested */
                s=sqrt(n); /* this calaulates and stores in 's' the new root of number 'n' */
                if ( p[c] <= s && p[c] != x ) /* 'The Magic Setting' tests the next prime number candidate p[c] and if passed it updates the prime number x */
                {
                    x=p[c];
                    ++c;
                }
                j=1;
                /* these steps sets the next number n to be tested and finds the next prime number x if possible for the new number 'n' and also resets j to 1 for the new cycle */
                continue; /* and this restarts the loop for the new cycle */
            }
            // confirmation test for the prime number candidate n
            else if ( n%p[j]!=0 && p[j]==x )
            {
                /* these steps execute if the number is found to be prime */
                p[m]=n;
                printf("%10dth:%10d\n",m,p[m]);
                ++n;
                s = sqrt(n);
                ++m;
                j=1;
                /* these steps stores and prints the new prime number and moves the 'm' counter up and also sets the next number n to be tested and also resets j to 1 for the new cycle */
                continue; /* and this restarts the loop */
                /* the next number which will be a even and non-prime will trigger the magic setting in the next cycle and therfore we do not have to add another magic setting here*/
            }
            ++j; /* increases p[j] to next prime number in the array for the next cycle testing of the number 'n' */
            // if the cycle reaches this point that means the number 'n' was neither divisible by p[j] nor was it a prime number
            // and therfore it will test the same number 'n' again in the next cycle with a bigger prime number
    }
    // the loops ends
    printf("  All done !!\n");
    end = clock();
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
    printf("  Time taken : %lf sec\n",cpu_time_used);
}

Вот код, который я сделал:


enter code here
#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;


int main() {
/* Enter your code here. Read input from STDIN. Print output to STDOUT*/   

unsigned long int n;

int prime(unsigned long int);

scanf("%ld",&n);

unsigned long int val;

for(unsigned long int i=0;i<n;i++)
{
    int flag=0;

    scanf("%ld",&val);

    flag=prime(val);

    if(flag==1)
        printf("yes\n");

    else
        printf("no\n");
}

return 0;

}

int prime(unsigned long int n)
{

if(n==2) return 1;

else if (n == 1||n%2==0)  return 0;

for (unsigned long int i=3; i<=sqrt(n); i+=2)
    if (n%i == 0)
        return 0;

return 1;
}

Использование метода Array.prototype.find () в Javascript. 2214,486 мс

function isPrime (number) {

  function prime(element) {
    let start = 2;
    while (start <= Math.sqrt(element)) {
      if (element % start++ < 1) {
        return false;
      }
    }
    return element > 1;
  }

  return [number].find(prime)

}

function logPrimes (n) {

  let count = 0
  let nth = n

  let i = 0
  while (count < nth) {
    if (isPrime(i)) {
      count++
      console.log('i', i) //NOTE: If this line is ommited time to find 10,000th prime is 121.157ms
      if (count === nth) {
        console.log('while i', i)
        console.log('count', count)
      }
    }
    i++
  }

}

console.time(logPrimes)

logPrimes(10000)

console.timeEnd(logPrimes) // 2214.486ms

Я могу дать вам несколько советов, вы должны это реализовать.

  1. Для каждого числа возьмите половину этого числа. Например, для проверки 21 получите остаток, разделив его на диапазон 2-10.
  2. Если это нечетное число, делите только на нечетное, и наоборот. Например, для 21, разделите только на 3, 5, 7, 9.

Самый эффективный метод, до которого я до сих пор добрался.

Поскольку вам нужны только первые 10000 простых чисел, вместо того, чтобы кодировать сложный алгоритм, я предлагаю следующее

boolean isPrime(int n){
//even but is prime
    if(n==2)
        return true;
//even numbers filtered already 
    if(n==0 || n==1 || n%2==0)
        return false;

// loop for checking only odd factors
// i*i <= n  (same as i<=sqrt(n), avoiding floating point calculations)
    for(int i=3 ; i*i <=n ; i+=2){
    // if any odd factor divides n then its not a prime!
        if(n%i==0)
            return false;
    }
// its prime now
    return true;
}

теперь звоните так просто, как вам нужно

for(int i=1 ; i<=1000 ; i++){
    if(isPrime(i)){
       //do something
    }
}

Deque решета алгоритма упомянутого BenGoldberg заслуживает более подробного рассмотрения, не только потому , что это очень элегантно , но и потому , что иногда может быть полезны на практике ( в отличии от Решета Аткина, который является чисто академическим упражнением).

Основная идея алгоритма deque sieve заключается в использовании небольшого скользящего сита, которое достаточно велико, чтобы содержать хотя бы одно отдельное кратное для каждого из текущих «активных» простых множителей, то есть тех простых чисел, квадрат которых не превышает наименьшее число. в настоящее время представлено движущимся ситом. Еще одно отличие SoE состоит в том, что сито deque сохраняет фактические факторы в слотах композитов, а не логические значения.

Алгоритм расширяет размер окна сита по мере необходимости, что приводит к довольно равномерной производительности в широком диапазоне, пока сито не начнет заметно превышать емкость кэша L1 ЦП. Последнее простое число, которое полностью подходит, - это 25 237 523 (1 579 791-е простое число), что дает приблизительную цифру для разумного рабочего диапазона алгоритма.

Алгоритм довольно прост и надежен, и он имеет даже производительность в гораздо более широком диапазоне, чем несегментированное Сито Эратосфена. Последний работает намного быстрее, если его сито полностью помещается в кеш, то есть до 2 ^ 16 для сита только для случайных чисел с булевыми значениями размера байта. Затем его производительность падает все больше и больше, хотя он всегда остается значительно быстрее, чем deque, несмотря на недостатки (по крайней мере, в компилируемых языках, таких как C/C++, Pascal или Java / C#).

Вот визуализация алгоритма deque sieve на C#, потому что я считаю этот язык - несмотря на его многочисленные недостатки - гораздо более практичным для создания прототипов алгоритмов и экспериментов, чем в высшей степени громоздкий и педантичный C++. (Замечание: я использую бесплатный LINQPad, который позволяет погрузиться прямо в него, без всякого беспорядка с настройкой проектов, make-файлов, каталогов и т. Д., И он дает мне ту же степень интерактивности, что и приглашение Python).

В C# нет явного типа deque, но простой List<int> работает достаточно хорошо для демонстрации алгоритма.

Примечание: эта версия не использует двухстороннюю очередь для простых чисел, потому что просто не имеет смысла извлекать sqrt (n) из n простых чисел. Что хорошего в том, чтобы удалить 100 простых чисел и оставить 9900? По крайней мере, таким образом все простые числа собираются в аккуратный вектор, готовый для дальнейшей обработки.

static List<int> deque_sieve (int n = 10000)
{
    Trace.Assert(n >= 3);

    var primes = new List<int>()  {  2, 3  };
    var sieve = new List<int>()  {  0, 0, 0  };

    for (int sieve_base = 5, current_prime_index = 1, current_prime_squared = 9; ; )
    {
        int base_factor = sieve[0];

        if (base_factor != 0)
        {
            // the sieve base has a non-trivial factor - put that factor back into circulation
            mark_next_unmarked_multiple(sieve, base_factor);
        }
        else if (sieve_base < current_prime_squared)  // no non-trivial factor -> found a non-composite
        {
            primes.Add(sieve_base);

            if (primes.Count == n)
                return primes;
        }
        else // sieve_base == current_prime_squared
        {
            // bring the current prime into circulation by injecting it into the sieve ...
            mark_next_unmarked_multiple(sieve, primes[current_prime_index]);

            // ... and elect a new current prime
            current_prime_squared = square(primes[++current_prime_index]);
        }

        // slide the sieve one step forward
        sieve.RemoveAt(0);  sieve_base += 2;
    }
}

Вот две вспомогательные функции:

static void mark_next_unmarked_multiple (List<int> sieve, int prime)
{
    int i = prime, e = sieve.Count;

    while (i < e && sieve[i] != 0)
        i += prime;

    for ( ; e <= i; ++e)  // no List<>.Resize()...
        sieve.Add(0);

    sieve[i] = prime;
}

static int square (int n)
{
    return n * n;
}

Вероятно, самый простой способ понять алгоритм - представить его как специальное сегментированное сито Эратосфена с размером сегмента 1, сопровождаемое областью переполнения, где простые числа останавливаются, когда они стреляют через конец сегмента. За исключением того, что единственная ячейка сегмента (также известная как sieve[0] ) уже просеяна, когда мы доходим до нее, потому что она переехала, когда была частью области переполнения.

Число, которое представлено sieve[0] как, содержится в sieve_base, хотя sieve_front или window_base могло бы также быть хорошими именами, которые позволяют проводить параллели с кодом Бена или реализациями сегментированных / оконных сит.

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

Текущее простое число вступает в игру следующим образом. Простое число может стать текущим только после его собственного появления в потоке (т. Е. Когда оно было обнаружено как простое число, потому что оно не отмечено множителем), и оно будет оставаться текущим до точного момента, когда sieve[0] достигнет своего квадрата. Все младшие кратные этого простого числа должны были быть удалены из-за активности меньших простых чисел, как и в обычной SoE. Но ни одно из меньших простых чисел не может выйти за пределы квадрата, поскольку единственный фактор квадрата - это само простое число, и в данный момент оно еще не находится в обращении. Это объясняет действия, предпринимаемые алгоритмом в данном случае sieve_base == current_prime_squared (что sieve[0] == 0, кстати, подразумевает ).

Теперь этот случай sieve[0] == 0 && sieve_base < current_prime_squared легко объясним: это означает, что оно sieve_base не может быть кратным любому из простых чисел, меньших текущего простого числа, иначе оно было бы помечено как составное. Я также не могу быть кратным текущему простому числу, так как его значение меньше, чем квадрат текущего простого числа. Следовательно, это должен быть новый прайм.

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

Вот простое несегментированное сито Эратосфена, которое я обычно использую для просеивания простых чисел коэффициента в коротком диапазоне, то есть до 2 ^ 16. В этом посте я изменил его так, чтобы он работал больше 2 ^ 16, заменив int на ushort

static List<int> small_odd_primes_up_to (int n)
{
    var result = new List<int>();

    if (n < 3)
        return result;

    int sqrt_n_halved = (int)(Math.Sqrt(n) - 1) >> 1, max_bit = (n - 1) >> 1;
    var odd_composite = new bool[max_bit + 1];

    for (int i = 3 >> 1; i <= sqrt_n_halved; ++i)
        if (!odd_composite[i])
            for (int p = (i << 1) + 1, j = p * p >> 1; j <= max_bit; j += p)
                odd_composite[j] = true;

    result.Add(3);  // needs to be handled separately because of the mod 3 wheel

    // read out the sieved primes
    for (int i = 5 >> 1, d = 1; i <= max_bit; i += d, d ^= 3)
        if (!odd_composite[i])
            result.Add((i << 1) + 1);

    return result;
}

При просеивании первых 10000 простых чисел типичный кэш L1 размером 32 KiByte будет превышен, но функция все еще очень быстрая (доли миллисекунды даже в C#).

Если вы сравните этот код с deque sieve, то легко увидите, что операции deque sieve намного сложнее, и он не может эффективно амортизировать свои накладные расходы, потому что он всегда выполняет кратчайший возможный отрезок пересечений подряд (ровно одно списание после пропуска всех уже вычеркнутых кратных).

Примечание: код C# использует int вместо этого, uint потому что более новые компиляторы имеют привычку генерировать некачественный код uint, вероятно, для того, чтобы подтолкнуть людей к целым числам со знаком ... В версии кода выше C++, которую я использовал unsigned, естественно; тест должен был быть на C++, потому что я хотел, чтобы он был основан на предположительно адекватном типе двухсторонней очереди ( std::deque<unsigned> ; прироста производительности от использования не наблюдалось unsigned short ). Вот цифры для моего ноутбука Haswell (VC++ 2015 / x64):

deque vs simple: 1.802 ms vs 0.182 ms
deque vs simple: 1.836 ms vs 0.170 ms 
deque vs simple: 1.729 ms vs 0.173 ms

Примечание: время C# почти в два раза больше тайминга C++, что довольно хорошо для C# и показывает, что List<int> это не сутулость, даже если использовать его как двухстороннюю очередь.

Простой код сита по-прежнему выталкивает двухстороннюю очередь из воды, даже если она уже работает за пределами своего нормального рабочего диапазона (размер кеш-памяти L1 превышен на 50%, с сопутствующей перегрузкой кеш-памяти). Преобладающей частью здесь является считывание отсеянных простых чисел, и на это не сильно влияет проблема с кешем. В любом случае функция была разработана для просеивания факторов факторов, то есть уровня 0 в трехуровневой иерархии сита, и обычно она должна возвращать только несколько сотен факторов или небольшое количество тысяч. Отсюда его простота.

Производительность может быть улучшена более чем на порядок за счет использования сегментированного сита и оптимизации кода для извлечения просеянных простых чисел (ступенчатый мод 3 и дважды развернутый или модуль 15 и развернутый один раз), и еще больше производительности можно выжать из код, используя колесо mod 16 или mod 30 со всеми обрезками (т.е. полная развертка для всех остатков). Нечто подобное объясняется в моем ответе на вопрос « Найти простое число с простыми позициями» на Code Review, где обсуждалась аналогичная проблема. Но трудно увидеть смысл в улучшении времени на доли миллисекунды для одноразовой задачи ...

Чтобы немного рассмотреть ситуацию, вот тайминги C++ для просеивания до 100000000:

deque vs simple: 1895.521 ms vs 432.763 ms
deque vs simple: 1847.594 ms vs 429.766 ms
deque vs simple: 1859.462 ms vs 430.625 ms

В отличие от этого, сегментированное решето в C# с несколькими прибамбасами выполняет ту же работу за 95 мс (тайминги C++ недоступны, поскольку в настоящий момент я выполняю задачи кода только на C#).

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

Кроме того, во многих случаях, указанных другими респондентами в этой теме, вероятно, преобладает время вывода . Это совершенно другая война, где моим основным оружием является такой простой класс:

class CCWriter
{
    const int SPACE_RESERVE = 11;  // UInt31 + '\n'

    public static System.IO.Stream BaseStream;
    static byte[] m_buffer = new byte[1 << 16];  // need 55k..60k for a maximum-size range
    static int m_write_pos = 0;
    public static long BytesWritten = 0;         // for statistics

    internal static ushort[] m_double_digit_lookup = create_double_digit_lookup();

    internal static ushort[] create_double_digit_lookup ()
    {
        var lookup = new ushort[100];

        for (int lo = 0; lo < 10; ++lo)
            for (int hi = 0; hi < 10; ++hi)
                lookup[hi * 10 + lo] = (ushort)(0x3030 + (hi << 8) + lo);

        return lookup;
    }

    public static void Flush ()
    {
        if (BaseStream != null && m_write_pos > 0)
            BaseStream.Write(m_buffer, 0, m_write_pos);

        BytesWritten += m_write_pos;
        m_write_pos = 0;
    }

    public static void WriteLine ()
    {
        if (m_buffer.Length - m_write_pos < 1)
            Flush();

        m_buffer[m_write_pos++] = (byte)'\n';
    }

    public static void WriteLinesSorted (int[] values, int count)
    {
        int digits = 1, max_value = 9;

        for (int i = 0; i < count; ++i)
        {
            int x = values[i];

            if (m_buffer.Length - m_write_pos < SPACE_RESERVE)
                Flush();

            while (x > max_value)
                if (++digits < 10)
                    max_value = max_value * 10 + 9;
                else
                    max_value = int.MaxValue;               

            int n = x, p = m_write_pos + digits, e = p + 1;

            m_buffer[p] = (byte)'\n';

            while (n >= 10)
            {
                int q = n / 100, w = m_double_digit_lookup[n - q * 100];
                n = q;
                m_buffer[--p] = (byte)w;
                m_buffer[--p] = (byte)(w >> 8);
            }

            if (n != 0 || x == 0)
                m_buffer[--p] = (byte)((byte)'0' + n);

            m_write_pos = e;
        }
    }
}

Для записи 10000 (отсортированных) чисел требуется менее 1 мс. Это статический класс, потому что он предназначен для текстового включения в отправку задач кодирования с минимумом суеты и нулевыми накладными расходами.

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

Это старый вопрос, но здесь что-то не хватает ...

Для простых чисел это небольшое пробное деление - не то медленно ... Есть только 25 простых чисел под 100. С таким малым количеством простых чисел в тесте, и такие маленькие штрихи, мы можем вытащить аккуратный трюк!

Если a взаимно просто с b, то gcd ab = 1. Взаимно прост. Веселое слово. Означает, что в нем нет основных факторов . Таким образом, мы можем проверить делимость на несколько простых чисел с помощью одного вызова GCD. Как много? Итак, произведение первых 15 простых чисел меньше 2 ^ 64. И произведение следующих 10 также меньше 2 ^ 64. Это все 25, что нам нужно. Но стоит ли оно того?

Давайте посмотрим:

check x = null $ filter ((==0) . (x `mod`)) $ [<primes up to 101>]
Prelude> length $ filter check [101,103..85600]
>>> 9975
(0.30 secs, 125,865,152 bytes

a = 16294579238595022365 :: Word64
b = 14290787196698157718
pre = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
primes = (pre ++) $ filter ((==1) . gcd a) $ filter ((==1) . gcd b) [99,101..85600]
main = print $ length primes

Prelude> main
>>> 10000
(0.05 secs, 36,387,520 bytes)

Это 6-кратное улучшение.

( length означает принудительное вычисление списка. По умолчанию Haskell печатает вещи по 1 символу Unicode за раз, и поэтому фактически печатает списка будет либо доминировать во времени, либо преобладать над количеством фактического используемого кода.)

Конечно, это работает в GHCi - реплике, выполняющей интерпретируемый код - на старом ноутбуке, и он не интерпретирует ни одно из этих чисел как int64 s или даже BigInt s, и не будет, даже если вы попросите его (ну, вы можете заставить его , но это некрасиво и не особо помогает). Он интерпретирует каждое отдельное число там как обобщенные целочисленные вещи, которые можно специализировать для определенного типа с помощью поиска по словарю, и он просматривает связанный список (который здесь не объединен, поскольку он не скомпилирован) 3 раза. Интересно, что ручное объединение двух фильтров фактически замедляет его в REPL.

Скомпилируем:

...\Haskell\8.6\Testbed>Primes.exe +RTS -s
10000
606,280 bytes allocated in the heap
Total   time    0.000s  (  0.004s elapsed)

Используя отчет RTS, поскольку Windows. Некоторые строки обрезаны, потому что они не имеют отношения к делу - это были другие данные GC или измерения только части выполнения, и вместе они составляют 0,004 с (или меньше). Это также не постоянное сворачивание, потому что Haskell на самом деле этого не делает. Если мы постоянно сворачиваем себя ( main = print 10000 ), мы получаем значительно меньшее выделение:

...Haskell\8.6\Testbed>Primes.exe +RTS -s
10000
47,688 bytes allocated in the heap
Total   time    0.000s  (  0.001s elapsed)

Буквально достаточно, чтобы загрузить среду выполнения, а затем обнаружить, что ничего не остается, кроме как вывести число и выйти. Добавим факторизацию колеса:

wheel = scanl (+) 7 $ cycle [4, 2, 4, 2, 4, 6, 2, 6]
primes = (pre ++) $ filter ((==1) . gcd a) $ filter ((==1) . gcd b) $ takeWhile (<85600) wheel

Total   time    0.000s  (  0.003s elapsed)

Уменьшено примерно на 1/3 по сравнению с нашим эталоном main = print 10000, но определенно есть место для дополнительной оптимизации. Например, он фактически остановился, чтобы выполнить сборку мусора, в то время как при настройке не должно быть никакого использования кучи. По какой-то причине компиляция для профилирования здесь фактически сокращает время выполнения до 2 миллисекунд:

Tue Nov 12 21:13 2019 Time and Allocation Profiling Report  (Final)

   Primes.exe +RTS -p -RTS

total time  =        0.00 secs   (2 ticks @ 1000 us, 1 processor)
total alloc =     967,120 bytes  (excludes profiling overheads)

Я собираюсь оставить это как есть, я почти уверен, что случайный джиттер начинает преобладать.

Я работаю над поиском простых чисел около года. Вот что я нашел самым быстрым:

import static java.lang.Math.sqrt;
import java.io.PrintWriter;
import java.io.File;
public class finder {
    public static void main(String[] args) {
        primelist primes = new primelist();
        primes.insert(3);
        primes.insert(5);
        File file = new File("C:/Users/Richard/Desktop/directory/file0024.txt");
        file.getParentFile().mkdirs();
        long time = System.nanoTime();
        try{
            PrintWriter printWriter = new PrintWriter ("file0024.txt"); 
            int linenum = 0;
            printWriter.print("2");
            printWriter.print (" , ");
            printWriter.print("3");
            printWriter.print (" , ");
            int up;
            int down;           
            for(int i =1; i<357913941;i++){//
                if(linenum%10000==0){
                    printWriter.println ("");
                    linenum++;
                }
                down = i*6-1;
                if(primes.check(down)){
                    primes.insert(down);
                    //System.out.println(i*6-1);
                    printWriter.print ( down );
                    printWriter.print (" , ");
                    linenum++;  
                }
                up = i*6+1;
                if(primes.check(up)){
                    primes.insert(up);
                    //System.out.println(i*6+1);
                    printWriter.print ( up );
                    printWriter.print (" , ");
                    linenum++;  
                }
            }
            printWriter.println ("Time to execute");
            printWriter.println (System.nanoTime()-time);
            //System.out.println(primes.length);
            printWriter.close ();
        }catch(Exception e){}
    } 
}
class node{
    node next;
    int x;
    public node (){
        node next;
        x = 3;
    }
    public node(int z) {
        node next;
        x = z;
    }
}
class primelist{
    node first;
    int length =0;
    node current;
    public void insert(int x){
        node y = new node(x);
        if(current == null){
            current = y;
            first = y;
        }else{
            current.next = y;
            current = y;
        }
        length++;
    }
    public boolean check(int x){
        int p = (int)sqrt(x);
        node y = first;
        for(int i = 0;i<length;i++){
            if(y.x>p){
                return true;
            }else if(x%y.x ==0){
                return false;
            }
            y = y.next;
        }
        return true;
    }
}

1902465190909 наносекунд, чтобы добраться до 2147483629, начиная с 2.

def compute_primes(bound):
"""
Return a list of the prime numbers in range(2, bound)
Implement the Sieve of Eratosthenes
https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
"""
primeNumber = [True for i in range(bound + 1)]
start_prime_number = 2
primes = []
while start_prime_number * start_prime_number <=bound:
    # If primeNumber[start_prime_number] is not changed, then it is a prime
    if primeNumber[start_prime_number]:
        # Update all multiples of start_prime_number
        for i in range(start_prime_number * start_prime_number, bound + 1, start_prime_number):
            primeNumber[i] = False
    start_prime_number += 1

# Print all prime numbers
for start_prime_number in range(2, bound + 1):
    if primeNumber[start_prime_number]:
        primes.append(start_prime_number)

return primes

печать (len (compute_primes (200)))

печать (len (compute_primes (2000)))

GateKiller , как насчет добавления break к этому if в foreach цикле? Это ускорит вещи много , потому что если , как 6 делится на 2 вам не нужно проверить с 3 и 5. (я бы голосовать ваше решение в любом случае , если у меня был достаточно репутации :-) ...)

ArrayList primeNumbers = new ArrayList();

for(int i = 2; primeNumbers.Count < 10000; i++) {
    bool divisible = false;

    foreach(int number in primeNumbers) {
        if(i % number == 0) {
            divisible = true;
            break;
        }
    }

    if(divisible == false) {
        primeNumbers.Add(i);
        Console.Write(i + " ");
    }
}

Адаптированный и продолженный GateKiller , вот последняя версия, которую я использовал.

    public IEnumerable<long> PrimeNumbers(long number)
    {
        List<long> primes = new List<long>();
        for (int i = 2; primes.Count < number; i++)
        {
            bool divisible = false;

            foreach (int num in primes)
            {
                if (i % num == 0)
                    divisible = true;

                if (num > Math.Sqrt(i))
                    break;
            }

            if (divisible == false)
                primes.Add(i);
        }
        return primes;
    }

Это в основном то же самое, но я добавил предложение «break on Sqrt» и изменил некоторые из переменных, чтобы он лучше подходил для меня. (Я работал над Эйлером, и мне нужно было 10001-е простое число)

В Haskell мы можем почти дословно записать математическое определение решета Эратосфена: простые числа - это натуральные числа больше 1 без каких-либо составных чисел, где составные числа находятся путем перечисления кратных каждому простому числу »:

import Data.List.Ordered (minus, union)

primes = 2 : minus [3..] (foldr (\p r -> p*p : union [p*p+p, p*p+2*p..] r)
                                [] primes)

primes !! 10000 почти мгновенно.

Использованная литература:


Приведенный выше код легко настроить для работы только с коэффициентами primes = 2 : 3 : minus [5,7..] (foldr (\p r -> p*p : union [p*p+2*p, p*p+4*p..] r) [] (tail primes)). Сложность времени значительно улучшается (почти до логарифмического коэффициента выше оптимального) за счет сворачивания в древовидную структуру, а сложность пространства значительно улучшается за счет многоступенчатого производства простых чисел в

primes = 2 : _Y ( (3:) . sieve 5 . _U . map (\p -> [p*p, p*p+2*p..]) )
  where
    _Y g = g (_Y g)                        -- non-sharing fixpoint combinator
    _U ((x:xs):t) = x : (union xs . _U . pairs) t       -- ~= nub.sort.concat
    pairs    (xs:ys:t)  = union xs ys : pairs t
    sieve k [email protected](x:xs) | k < x      = k : sieve (k+2) s   -- ~= [k,k+2..]\\s,
                     | otherwise  =     sieve (k+2) xs  --   when s⊂[k,k+2..]

(В Haskell круглые скобки используются для группировки, вызов функции обозначается просто сопоставлением, (:)является оператором cons для списков и (.)оператором функциональной композиции :) (f . g) x = (\y -> f (g y)) x = f (g x).

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

/^1?$|^(11+?)\1+$/

Эти тесты , если для строки , состоящей из к « 1 » х, к это не простое число (т.е. состоит ли строка из одного « 1 » или любое количество « 1 » S , которые могут быть выражены в виде п -ичный продукта).

Я адаптировал код, найденный в CodeProject, для создания следующего:

ArrayList primeNumbers = new ArrayList();

for(int i = 2; primeNumbers.Count < 10000; i++) {
    bool divisible = false;

    foreach(int number in primeNumbers) {
        if(i % number == 0) {
            divisible = true;
        }
    }

    if(divisible == false) {
        primeNumbers.Add(i);
        Console.Write(i + " ");
    }
}

Тестирование этого на моем сервере ASP.NET заняло около 1 минуты.

Сито Эратосфена - лучший выбор из-за его простоты и скорости. Моя реализация на C

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>

int main(void)
{
    unsigned int lim, i, j;

    printf("Find primes upto: ");
    scanf("%d", &lim);
    lim += 1;
    bool *primes = calloc(lim, sizeof(bool));

    unsigned int sqrtlim = sqrt(lim);
    for (i = 2; i <= sqrtlim; i++)
        if (!primes[i])
            for (j = i * i; j < lim; j += i)
                primes[j] = true;

    printf("\nListing prime numbers between 2 and %d:\n\n", lim - 1);
    for (i = 2; i < lim; i++)
        if (!primes[i])
            printf("%d\n", i);

    return 0;
}

Время ЦП для поиска простых чисел (на Pentium Dual Core E2140 1,6 ГГц, с использованием одноядерного процессора)

~ 4 с для лим. = 100000000

Используя GMP, можно было бы написать следующее:

#include <stdio.h>
#include <gmp.h>

int main() {
  mpz_t prime;
  mpz_init(prime);
  mpz_set_ui(prime, 1);
  int i;
  char* num = malloc(4000);
  for(i=0; i<10000; i++) {
    mpz_nextprime(prime, prime);
    printf("%s, ", mpz_get_str(NULL,10,prime));
  }
}

На моем Macbook Pro с тактовой частотой 2,33 ГГц он работает следующим образом:

time ./a.out > /dev/null

real    0m0.033s
user    0m0.029s
sys    0m0.003s

Вычисление 1000000 простых чисел на одном ноутбуке:

time ./a.out > /dev/null

real    0m14.824s
user    0m14.606s
sys     0m0.086s

GMP оптимизирован для такого рода вещей. Если вы действительно не хотите разбираться в алгоритмах, написав свой собственный, вам рекомендуется использовать libGMP под C.

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

http://primes.utm.edu/lists/small/10000.txt

Сито кажется неправильным ответом. Сито дает вам простые числа до числа N , а не первые N простых чисел. Запустите @Imran или @Andrew Szeto, и вы получите простые числа до N.

Сито может по-прежнему использоваться, если вы продолжаете пытаться использовать сита для все больших чисел, пока не достигнете определенного размера вашего набора результатов, и использовать некоторое кеширование уже полученных чисел, но я считаю, что это все равно будет не быстрее, чем такое решение, как @ Pat's .

Вот сито Эратосфена, которое я написал в PowerShell несколько дней назад. У него есть параметр для определения количества простых чисел, которые должны быть возвращены.

#
# generate a list of primes up to a specific target using a sieve of eratosthenes
#
function getPrimes { #sieve of eratosthenes, http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
    param ($target,$count = 0)
    $sieveBound = [math]::ceiling(( $target - 1 ) / 2) #not storing evens so count is lower than $target
    $sieve = @($false) * $sieveBound
    $crossLimit = [math]::ceiling(( [math]::sqrt($target) - 1 ) / 2)
    for ($i = 1; $i -le $crossLimit; $i ++) {
        if ($sieve[$i] -eq $false) {
            $prime = 2 * $i + 1
            write-debug "Found: $prime"
            for ($x = 2 * $i * ( $i + 1 ); $x -lt $sieveBound; $x += 2 * $i + 1) {
                $sieve[$x] = $true
            }
        }
    }
    $primes = @(2)
    for ($i = 1; $i -le $sieveBound; $i ++) {
        if($count -gt 0 -and $primes.length -ge $count) {
            break;
        }
        if($sieve[$i] -eq $false) {
            $prime = 2 * $i + 1
            write-debug "Output: $prime"
            $primes += $prime
        }
    }
    return $primes
}

Я написал это с помощью Python, так как только начал его изучать, и он отлично работает. 10 000-е простое число генерируется этим кодом, как указано в http://primes.utm.edu/lists/small/10000.txt . Чтобы проверить, n является ли число простым, разделите n его на числа от 2 до sqrt(n) . Если любое из этого диапазона чисел идеально делится, n значит, оно не простое.

import math
print ("You want prime till which number??")
a = input()
a = int(a)
x = 0
x = int(x)
count = 1
print("2 is prime number")
for c in range(3,a+1):
    b = math.sqrt(c)
    b = int(b)
    x = 0
    for b in range(2,b+1):
        e  = c % b
        e = int(e)
        if (e == 0):
            x = x+1
    if (x == 0):
        print("%d is prime number" % c)
        count = count + 1
print("Total number of prime till %d is %d" % (a,count))