¿Cuál es el algoritmo más rápido para encontrar números primos?


184

¿Cuál es el algoritmo más rápido para encontrar números primos usando C ++? ¡He usado el algoritmo de tamiz pero todavía quiero que sea más rápido!


Un viejo artículo que encontré, pero parece interesante: Diversión con números primos
Mvcoile

29
@Jaider esto falla para números tan bajos como 7 (111). También falla para 1001 = 9. Y claramente falla para casi todos los números primos en general (no cubre el caso 2 ^ p - 1, que son números primos de Mersenne - ejemplos generados de forma clásica - que siempre tendrán la forma 111 ... 1)
Daniel Kats

1
@ Kasperasky - ¿No mencionaste qué tamiz? ¡Probablemente te refieres a Sieve of Eranthoses!
user2618142

Tamiz del algoritmo de Eratóstenes
Emad Aghayi

Respuestas:


79

Una implementación muy rápida del Tamiz de Atkin es el primogénito de Dan Bernstein . Este tamiz es más eficiente que el Tamiz de Eratóstenes . Su página tiene información de referencia.


10
En realidad, no creo que Primegen sea el más rápido, o incluso el segundo más rápido; yafu y primesieve son ambos más rápidos en general, creo, y ciertamente superan los 2 ^ 32. Ambos son tamices (modificados) de Eratóstenes en lugar del tamiz Atkin-Bernstein.
Charles

66
Primesieve Sieve of Eratosthenes (SoE) es el algoritmo más rápido posible y siempre será más rápido que cualquier implementación del Sieve of Atkin SoA, incluido el de Bernstein como se vincula en esta respuesta porque primesieve reduce el número de operaciones en comparación con SoA: para el 32- rango de número de bits (2 ^ 32 - 1), primesieve realiza alrededor de 1.200 millones de matanzas, mientras que SoA realiza un total de aproximadamente 1.400 millones de operaciones combinadas de alternar y cuadrado libre, ambas operaciones tienen aproximadamente la misma complejidad y pueden optimizarse en aproximadamente el mismo camino.
GordonBGood

77
Continuación: Bernstein solo comparó el SoE utilizando la misma factorización efectiva de la rueda que para el SoA, que es una rueda 2; 3; 5, cuyo uso da como resultado 1,83 mil millones de descartes en el rango de números de 32 bits; esto hace que el SoA sea un 30% más rápido al comparar esta versión restringida de SoE para otras optimizaciones equivalentes. Sin embargo, el algoritmo primesieve utiliza una rueda 2; 3; 5; 7 en combinación con un pre-sacrificio de 2; 3; 5; 7; 11; 13; 17 segmentos de rueda para reducir el número de operaciones a alrededor de 1.200 millones. 16,7% más rápido que SoA con optimizaciones de bucle de operación equivalentes.
GordonBGood

66
Continuación2: El SoA no puede tener una factorización de rueda de factor más alta utilizada para hacer una gran diferencia ya que la rueda de factorización 2; 3; 5 es una parte "integrada" del algoritmo.
GordonBGood

44
@Eamon Nerbonne, WP es correcto; sin embargo, solo tener una complejidad computacional ligeramente mejor no hace que los algoritmos sean más rápidos para uso general. En estos comentarios, me estoy refiriendo a que la factorización máxima de la rueda del Tamiz de Eratóstenes (SoE) (que no es posible para el Tamiz de Atkin-SoA) hace que las operaciones del SoE sean ligeramente menores hasta un rango de aproximadamente mil millones. Muy por encima de ese punto, generalmente se necesita usar la segmentación de página para superar las limitaciones de memoria, y ahí es donde falla el SoA, tomando cantidades cada vez mayores de sobrecarga constante con un rango creciente.
GordonBGood 01 de

29

Si tiene que ser realmente rápido, puede incluir una lista de números primos:
http://www.bigprimes.net/archive/prime/

Si solo tiene que saber si un número determinado es un número primo, hay varias pruebas principales enumeradas en wikipedia . Probablemente sean el método más rápido para determinar si los números grandes son números primos, especialmente porque pueden decirle si un número no es primo.


2
¿Una lista de todos los números primos? Creo que te refieres a una lista de los primeros números primos ... :)
j_random_hacker

9
Si llamas a 100000000 unos pocos, entonces sí. :)
Georg Schölly

58
seguramente 100000000 es "unos pocos" en comparación con el infinito;)
Timofey

9
¿Por qué crees que el Tamiz de Atkin (SoA) es más rápido que el Tamiz de Eratóstenes (SoE)? Ciertamente no es cuando uno solo implementa un programa usando el pseudocódigo como en el artículo de Wikipedia que vinculó. Si el SoE se implementa con un nivel de optimizaciones posibles similar al que se usa con el SoA, entonces hay un poco menos de operaciones para rangos de tamizado muy grandes para SoA que para SoE, pero esa ganancia generalmente es más que compensada por la mayor complejidad y sobrecarga de factor constante adicional de esta complejidad computacional de tal manera que para aplicaciones prácticas el SoE es mejor.
GordonBGood

26

Él, sé que soy un nigromante de preguntas que responde a viejas preguntas, pero acabo de encontrar esta pregunta buscando en la red formas de implementar pruebas de números primos eficientes.

Hasta ahora, creo que el algoritmo de prueba de número primo más rápido es Strong Probable Prime (SPRP). Cito de los foros de Nvidia CUDA:

Uno de los problemas de nicho más prácticos en la teoría de números tiene que ver con la identificación de números primos. Dado N, ¿cómo puede determinar eficientemente si es primo o no? Este no es solo un problema teórico, puede ser un problema real que se necesita en el código, tal vez cuando necesite encontrar dinámicamente un tamaño de tabla hash principal dentro de ciertos rangos. Si N es algo del orden de 2 ^ 30, ¿realmente quieres hacer 30000 pruebas de división para buscar algún factor? Obviamente no.

La solución práctica común a este problema es una prueba simple llamada prueba probable probable de Euler, y una generalización más poderosa llamada prima probable fuerte (SPRP). Esta es una prueba que para un entero N puede clasificarlo probabilísticamente como primo o no, y las pruebas repetidas pueden aumentar la probabilidad de corrección. La parte lenta de la prueba en sí misma implica principalmente calcular un valor similar al módulo A ^ (N-1) N. Cualquiera que implemente variantes de cifrado de clave pública RSA ha utilizado este algoritmo. Es útil tanto para enteros enormes (como 512 bits) como para entradas normales de 32 o 64 bits.

La prueba se puede cambiar de un rechazo probabilístico a una prueba definitiva de primalidad precalculando ciertos parámetros de entrada de prueba que se sabe que siempre tienen éxito para rangos de N. Desafortunadamente, el descubrimiento de estas "pruebas más conocidas" es efectivamente una búsqueda de un gran ( de hecho infinito) dominio. En 1980, Carl Pomerance (famoso por ser el factorizador de RSA-129 con su algoritmo Quadratic Seive) creó una primera lista de pruebas útiles. Más tarde, Jaeschke mejoró significativamente los resultados en 1993. En 2004, Zhang y Tang mejoraron la teoría. y límites del dominio de búsqueda. Greathouse y Livingstone han publicado los resultados más modernos hasta ahora en la web, en http://math.crg4.com/primes.html , los mejores resultados de un gran dominio de búsqueda.

Consulte aquí para obtener más información: http://primes.utm.edu/prove/prove2_3.html y http://forums.nvidia.com/index.php?showtopic=70483

Si solo necesita una forma de generar números primos muy grandes y no le importa generar todos los números primos <un número entero n, puede usar la prueba de Lucas-Lehmer para verificar los números primos de Mersenne. Un número primo de Mersenne tiene la forma de 2 ^ p -1. Creo que la prueba de Lucas-Lehmer es el algoritmo más rápido descubierto para los números primos de Mersenne.

Y si no solo desea utilizar el algoritmo más rápido sino también el hardware más rápido, intente implementarlo con Nvidia CUDA, escriba un núcleo para CUDA y ejecútelo en la GPU.

Incluso puede ganar algo de dinero si descubre números primos lo suficientemente grandes, EFF está dando premios de $ 50K a $ 250K: https://www.eff.org/awards/coop


17

Hay una prueba 100% matemática que verificará si un número Pes primo o compuesto, llamado Prueba de Primaria AKS .

El concepto es simple: dado un número P, si todos los coeficientes de (x-1)^P - (x^P-1)son divisibles por P, entonces Pes un número primo, de lo contrario es un número compuesto.

Por ejemplo, dado P = 3, daría el polinomio:

   (x-1)^3 - (x^3 - 1)
 = x^3 + 3x^2 - 3x - 1 - (x^3 - 1)
 = 3x^2 - 3x

Y los coeficientes son divisibles por 3, por lo tanto, el número es primo.

Y ejemplo donde P = 4, lo cual NO es primo produciría:

   (x-1)^4 - (x^4-1)
 = x^4 - 4x^3 + 6x^2 - 4x + 1 - (x^4 - 1)
 = -4x^3 + 6x^2 - 4x

Y aquí podemos ver que los coeficientes 6no son divisibles por 4, por lo tanto, NO son primos.

El polinomio (x-1)^Pva a P+1términos y se puede encontrar usando la combinación. Por lo tanto, esta prueba se ejecutará en O(n)tiempo de ejecución, por lo que no sé qué tan útil sería, ya que simplemente puede iterar ide 0 a py probar el resto.


55
AKS es un método muy lento en la práctica, no competitivo con otros métodos conocidos. El método que describe no es AKS sino un lema inicial que es más lento que la división de prueba no optimizada (como usted señala).
DanaJ

hola @Kousha, ¿qué significan las xsiglas? en (x-1)^P - (x^P-1). ¿tienes un código de muestra para esto? en C ++ para determinar si el entero es primo o no?
kiLLua

@kiLLua X es solo una variable. Es el coeficiente de X que determina si el número es primo o no. Y no, no tengo el código. No recomiendo usar este método para determinar si un número es primo o no. Este es solo un comportamiento matemático muy bueno de los números primos, pero por lo demás es increíblemente ineficiente.
Kousha

5

¿Es su problema decidir si un número particular es primo? Entonces necesitas una prueba de primalidad (fácil). ¿O necesita todos los números primos hasta un número dado? En ese caso, los tamices principales son buenos (fáciles, pero requieren memoria). ¿O necesitas los factores primos de un número? Esto requeriría factorización (difícil para grandes números si realmente quieres los métodos más eficientes). ¿Qué tan grandes son los números que estás viendo? 16 bits? 32 bits? ¿más grande?

Una forma inteligente y eficiente es calcular previamente las tablas de números primos y mantenerlas en un archivo utilizando una codificación de nivel de bits. El archivo se considera un vector de bit largo, mientras que el bit n representa el número entero n. Si n es primo, su bit se establece en uno y en cero en caso contrario. La búsqueda es muy rápida (calcula el desplazamiento de bytes y una máscara de bits) y no requiere cargar el archivo en la memoria.


Una buena prueba de primalidad es competitiva con la latencia de memoria principal para tablas principales que podrían ajustarse razonablemente, por lo que no usaría esto a menos que pueda encajar en L2.
Charles

3

Rabin-Miller es una prueba de primitiva probabilística estándar. (lo ejecuta K veces y el número de entrada es definitivamente compuesto, o probablemente sea primo con probabilidad de error 4 -K . (unos cientos de iteraciones y es casi seguro que le dice la verdad)

Hay una variante no probabilística (determinista) de Rabin Miller .

La Great Internet Mersenne Prime Search (GIMPS), que ha encontrado el récord mundial de primos probados más grandes (2 74,207,281 - 1 a partir de junio de 2017), utiliza varios algoritmos , pero estos son primos en formas especiales. Sin embargo, la página GIMPS anterior incluye algunas pruebas de primitivismo determinista generales. Parecen indicar que el algoritmo que es "más rápido" depende del tamaño del número que se probará. Si su número cabe en 64 bits, entonces probablemente no debería usar un método destinado a trabajar con números primos de varios millones de dígitos.


2

Depende de su aplicación. Hay algunas consideraciones:

  • ¿Necesita solo la información si algunos números son primos, necesita todos los números primos hasta cierto límite, o necesita (potencialmente) todos los números primos?
  • ¿Qué tan grandes son los números con los que tienes que lidiar?

Las pruebas de Miller-Rabin y análogas son solo más rápidas que un tamiz para números de más de cierto tamaño (en algún lugar alrededor de unos pocos millones, creo). Debajo de eso, usar una división de prueba (si solo tiene algunos números) o un tamiz es más rápido.


0

Te dejaré decidir si es el más rápido o no.

using System;
namespace PrimeNumbers
{

public static class Program
{
    static int primesCount = 0;


    public static void Main()
    {
        DateTime startingTime = DateTime.Now;

        RangePrime(1,1000000);   

        DateTime endingTime = DateTime.Now;

        TimeSpan span = endingTime - startingTime;

        Console.WriteLine("span = {0}", span.TotalSeconds);

    }


    public static void RangePrime(int start, int end)
    {
        for (int i = start; i != end+1; i++)
        {
            bool isPrime = IsPrime(i);
            if(isPrime)
            {
                primesCount++;
                Console.WriteLine("number = {0}", i);
            }
        }
        Console.WriteLine("primes count = {0}",primesCount);
    }



    public static bool IsPrime(int ToCheck)
    {

        if (ToCheck == 2) return true;
        if (ToCheck < 2) return false;


        if (IsOdd(ToCheck))
        {
            for (int i = 3; i <= (ToCheck / 3); i += 2)
            {
                if (ToCheck % i == 0) return false;
            }
            return true;
        }
        else return false; // even numbers(excluding 2) are composite
    }

    public static bool IsOdd(int ToCheck)
    {
        return ((ToCheck % 2 != 0) ? true : false);
    }
}
}

Toma aproximadamente 82 segundos encontrar e imprimir números primos dentro de un rango de 1 a 1,000,000, en mi computadora portátil Core 2 Duo con un procesador de 2.40 GHz. Y encontró 78,498 números primos.


3
Esto es demasiado lento. el problema es i <= (ToCheck / 3). debería ser i <= (ToCheck / i). con él, podría ejecutarse en 0.1 segundos en su lugar.
Will Ness

-1

Siempre uso este método para calcular números primos siguiendo el algoritmo de tamizado.

void primelist()
 {
   for(int i = 4; i < pr; i += 2) mark[ i ] = false;
   for(int i = 3; i < pr; i += 2) mark[ i ] = true; mark[ 2 ] = true;
   for(int i = 3, sq = sqrt( pr ); i < sq; i += 2)
       if(mark[ i ])
          for(int j = i << 1; j < pr; j += i) mark[ j ] = false;
  prime[ 0 ] = 2; ind = 1;
  for(int i = 3; i < pr; i += 2)
    if(mark[ i ]) ind++; printf("%d\n", ind);
 }

-3
#include<stdio.h>
main()
{
    long long unsigned x,y,b,z,e,r,c;
    scanf("%llu",&x);
    if(x<2)return 0;
    scanf("%llu",&y);
    if(y<x)return 0;
    if(x==2)printf("|2");
    if(x%2==0)x+=1;
    if(y%2==0)y-=1;
    for(b=x;b<=y;b+=2)
    {
        z=b;e=0;
        for(c=2;c*c<=z;c++)
        {
            if(z%c==0)e++;
            if(e>0)z=3;
        }
        if(e==0)
        {
            printf("|%llu",z);
            r+=1;
        }
    }
    printf("|\n%llu outputs...\n",r);
    scanf("%llu",&r);
}    

r se usa antes de ser inicializado
zumalifeguard

-3

No conozco ningún algoritmo predefinido, pero creé el mío, que es muy rápido. Puede procesar números de 20 dígitos en menos de 1 segundo. La capacidad máxima de este programa es 18446744073709551615. El programa es:

#include <iostream>
#include <cmath>
#include <stdlib.h>

using namespace std;

unsigned long long int num = 0;

bool prime() {
    if (num % 2 == 0 || num == 1) {
        return false;
    }

    unsigned long int square_root = sqrt(num);
    for (unsigned long int i = 3; i <= square_root; i += 2) {
        if (num % i == 0) {
            return false;
        }
    }

    return true;
}

int main() {
    do {
        system("cls");
        cout << "Enter number : ";
        cin >> num;

        if (prime()) {
            cout << "The number is a prime number" << endl << endl << endl << endl;
        } else {
            cout << "The number is not a prime number" << endl << endl << endl << endl;
        }
        system("pause");
    } while (1);

    return 0;
}

-4
#include <iostream>

using namespace std;

int set [1000000];

int main (){

    for (int i=0; i<1000000; i++){
        set [i] = 0;
    }
    int set_size= 1000;
    set [set_size];
    set [0] = 2;
    set [1] = 3;
    int Ps = 0;
    int last = 2;

    cout << 2 << " " << 3 << " ";

    for (int n=1; n<10000; n++){
        int t = 0;
        Ps = (n%2)+1+(3*n);
        for (int i=0; i==i; i++){
            if (set [i] == 0) break;
            if (Ps%set[i]==0){
                t=1;
                break;
            }
        }
        if (t==0){
            cout << Ps << " ";
            set [last] = Ps;
            last++;
        }
    }
    //cout << last << endl;


    cout << endl;

    system ("pause");
    return 0;
}

12
Esto debería ser una respuesta sobre "Cómo escribir código no estructurado sin usar GOTO". ¡Toda esta confusión solo para codificar una división de prueba simple! (n%2)+1+(3*n)aunque es un poco agradable. :)
Will Ness

1
@ Will Ness Hubiera rechazado esto como respuesta a esa pregunta; ¿Por qué usar un bucle for cuando una macro funcionará? :)
Rob Grant

-4

Sé que es algo más tarde, pero esto podría ser útil para las personas que llegan aquí desde las búsquedas. De todos modos, aquí hay algunos JavaScript que se basan en el hecho de que solo los factores primos deben ser probados, por lo que los primos anteriores generados por el código se reutilizan como factores de prueba para los posteriores. Por supuesto, todos los valores pares y mod 5 se filtran primero. El resultado estará en la matriz P, y este código puede procesar 10 millones de primos en menos de 1.5 segundos en una PC i7 (o 100 millones en aproximadamente 20). Reescrito en C, debería ser muy rápido.

var P = [1, 2], j, k, l = 3

for (k = 3 ; k < 10000000 ; k += 2)
{
  loop: if (++l < 5)
  {
    for (j = 2 ; P[j] <= Math.sqrt(k) ; ++j)
      if (k % P[j] == 0) break loop

    P[P.length] = k
  }
  else l = 0
}

2
Esto le dará muchos problemas si está generando una gran cantidad de números primos, y para las comparaciones, use mejor P [j] * P [j] <= k, porque sqrt es bastante lento
Simon

-11
#include<iostream>
using namespace std;

void main()
{
    int num,i,j,prime;
    cout<<"Enter the upper limit :";
    cin>>num;

    cout<<"Prime numbers till "<<num<<" are :2, ";

    for(i=3;i<=num;i++)
    {
        prime=1;
        for(j=2;j<i;j++)
        {
            if(i%j==0)
            {
                prime=0;
                break;
            }
        }

        if(prime==1)
            cout<<i<<", ";

    }
}

6060
esto es lo más lento que puedes hacer.
Will Ness

1
¡Esto es muy lento, si el límite superior es digamos 10000000, entonces este código consumirá mucho tiempo!
Dixit Singla

este código es O (N ^ 2 / log N). sin break;esto sería aún más lento, O (N ^ 2), pero eso ya podría verse como un error de codificación. guardar y probar con números primos es O (N ^ 2 / (log N) ^ 2), y probar solo con números primos debajo de la raíz cuadrada del número, es O (N ^ 1.5 / (log N) ^ 2).
Will Ness

@ WillNess Quizás un poco hiperbólico. Fácilmente podría haber iniciado el ciclo for desde 1 en lugar de 2, y agregar un j <= i en lugar de j <i. :)
Kenny Cason

3
No creo que esta publicación deba eliminarse, sirve como un contraejemplo valioso.
Will Ness
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.