Proyecto Euler Problema 10 - Algoritmo eficiente

Intenté el problema 10 del Proyecto Euler usando un algoritmo muy sencillo y el tiempo de ejecución parece horas. Así que busqué en Google un algoritmo eficiente y encontré esto por Pez shlomif. El código se reproduce a continuación:

int main(int argc, char * argv[])
{
    int p, i;
    int mark_limit;
    long long sum = 0;

    memset(bitmask, '\0', sizeof(bitmask));
    mark_limit = (int)sqrt(limit);

    for (p=2 ; p <= mark_limit ; p++)
    {
        if (! ( bitmask[p>>3]&(1 << (p&(8-1))) ) )
        {
            /* It is a prime. */
            sum += p;
            for (i=p*p;i<=limit;i+=p)
            {
                bitmask[i>>3] |= (1 << (i&(8-1)));
            }
        }
    }
    for (; p <= limit; p++)
    {
        if (! ( bitmask[p>>3]&(1 << (p&(8-1))) ) )
        {
            sum += p;
        }
    }

Tengo problemas para entender el código. Específicamente, ¿cómo puede este código de desplazamiento de bits determinar si un número es primo o no?

   if (! ( bitmask[p>>3]&(1 << (p&(8-1))) ) )
        {
            /* It is a prime. */
            sum += p;
            for (i=p*p;i<=limit;i+=p)
            {
                bitmask[i>>3] |= (1 << (i&(8-1)));
            }
        }

¿Puede alguien explicarme este bloque de código, especialmente esta parte? ( bitmask[p>>3]&(1 << (p&(8-1)? Muchísimas gracias.

preguntado el 28 de septiembre de 11 a las 05:09

Por cierto, puede obtener el mismo empaque en C ++ con bitset o vector (contenedor especializado) sin trucos. -

E incluso en C debería usar funciones que abstraigan el mapeo de bits a matrices. Escribir código como este es de mal estilo. -

2 Respuestas

El código es un Tamiz de Eratóstenes modificado. Está empaquetando un número en un bit: 0 = primo, 1 = compuesto. El cambio de bit es para llegar al bit correcto en la matriz de bytes.

 bitmask[p>>3]

es equivalente a

 bitmask[p / 8]

que selecciona el byte correcto en el bitmask[] formación.

(p&(8-1))

iguales p & 7, que selecciona los 3 bits inferiores de p. Esto es equivalente a p % 8

En general, estamos seleccionando bit (p % 8) de byte bitmask[p / 8]. Es decir, estamos seleccionando el bit en el bitmask[] matriz que representa el número p.

El 1 << (p % 8) establece un 1 bit correctamente ubicado en un byte. A continuación, se realiza una operación AND con el bitmask[p / 8] byte para ver si ese bit en particular está establecido o no, verificando así si p es un número primo.

La declaración general equivale a if (isPrime(p)), utilizando la parte ya terminada del tamiz para ayudar a extender el tamiz.

Respondido el 28 de Septiembre de 11 a las 16:09

¿Puede explicar más por qué se utiliza "8"? El número p puede ser mucho mayor, como 100000. ¿Qué tiene de especial "8" que se utiliza como "base" en estos cambios de bits? Además, por qué cuando 1<<(p%8) está anotado con el bitmask[p/8], podemos determinar si es un número primo o no? ¿Me estoy perdiendo algo de teoría matemática aquí? - Parada

@Standstill, el tamiz es una matriz lógica de bits. Si poco N está encendido, o es verdadero, entonces el número N es primordial. Estos bits se implementan como una matriz de bytes de 8 bits. De ahí proviene el número mágico 8. El tamiz podría implementarse con la misma facilidad con elementos de 16/32/64 bits, en cuyo caso la constante se cambiaría para que coincida. Las operaciones de enmascaramiento y desplazamiento de bits se utilizan para direccionar el byte deseado y luego el bit específico dentro. - Alto horno

@Standstill: @Blastfurnace es correcto, el 8 es el número de bits en un byte. Trabaje con algunos ejemplos a mano de p=5 a p=11 para tener una idea de las cosas. El AND es para comprobar si el bit en bitmask[p/8] Está establecido: 1&1=1, 0&1=0. Ese bit marca si o no p es primordial. Le sugiero que lea sobre el Tamiz de Eratóstenes. Escriba su propia implementación más simple que lo ayudará a ver lo que está haciendo el código aquí. - Rossum

Gracias. Creo que la información es suficiente para trabajar. - Parada

La máscara de bits actúa como una matriz de bits. Dado que no puede direccionar los bits individualmente, primero debe acceder al byte y luego modificar un poco dentro de él. Desplazarse a la derecha por 3 es lo mismo que dividir por 8, lo que lo coloca en el byte derecho. Luego, el resto lo coloca en su lugar.

x >> 3 es equivalente ax / 8

x & (8-1) es equivalente ax% 8

Pero en algunos sistemas más antiguos, las manipulaciones de bits pueden haber sido más rápidas.

La línea establece el i-ésimo bit, donde se ha determinado que i no es primo porque es un múltiplo de otro número primo:

bitmask[i>>3] |= (1 << (i&(8-1)));

Esta línea verifica que el bit pth no esté establecido, lo que significa que es primo, ya que si no fuera primo habría sido establecido por la línea anterior.

if (! ( bitmask[p>>3]&(1 << (p&(8-1))) ) )

Respondido el 28 de Septiembre de 11 a las 17:09

Las manipulaciones de bits nunca fueron más rápidas. los primeras Los compiladores de C pudieron hacerlo automáticamente. La mayoría de los compiladores lo hacen incluso con la optimización desactivada. - Dietrich Epp

@Dietrich Epp En realidad, todavía hay muchos compiladores que no pueden hacer esta optimización a menos que se les indique explícitamente. Si el rendimiento del programa es crítico (y solo entonces), y el código debe ser portátil, puede ser una buena idea optimizar la "división por turnos" manualmente, aunque hace que el código sea feo y mucho menos legible. - Lundin

@Lundin: Eso es algo sorprendente. ¿Puede dar algunos ejemplos? - Dietrich Epp

@Vaughn: Entonces, ¿cómo pueden x / 8 yx% 8 determinar el primo o no? - Parada

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.