El problema radica en la recursivamente función definida Prime(X,Y)
, pero también en el algoritmo utilizado. Solo hay demasiada recursión profundidad que el mecanismo de llamada a función de Java puede acomodarse antes de que se agote la pila de llamadas, causando el error de "desbordamiento de pila".
Es suficiente para probar la divisibilidad frente a todos los números por debajo de la raíz cuadrada del número que se está probando. En términos del código OP, eso significa comenzar no desde Prime(N,N-1)
, sino desde Prime(N, floor(sqrt(N+1)))
. Este cambio solo podría ser suficiente para evitar el error SO para esta tarea específica, ya que la profundidad de recursión cambiará de 10000 a solo 100.
Los problemas algorítmicos solo comienzan ahí. El código Prime(X,Y)
cuenta abajo, por lo tanto, primero pruebe el número por números más grandes. Pero los factores más pequeños se encuentran mucho más a menudo; el conteo se debe hacer desde el factor más pequeño posible, 2 (que se encuentra para el 50% de los números), hasta al sqrt
del número de candidato. La función debe volver a escribirse como un simple bucle while
en esta oportunidad, también.
La siguiente mejora fácil y obvia es ignorar por completo los números pares. 2 es conocido por ser primo; todos los otros pares no son. Eso significa que a partir del bucle de numberOfPrimes = 1; number = 3;
y contando por number += 2
para enumerar sólo los números impares, teniendo isPrime(N)
prueba su divisibilidad sólo por las números impares, así, en un bucle while
comenzando con X = 3
, las pruebas de N % X
y contando por X += 2
.
O en pseudocódigo (en realidad, Haskell), el código original es
main = print ([n | n<-[2..], isPrime(n)] !! 10000) where
isPrime(n) = _Prime(n-1) where
_Prime(y) = y==1 || (rem n y > 0 && _Prime(y-1))
-- 100:0.50s 200:2.57s 300:6.80s 10000:(projected:8.5h)
-- n^2.4 n^2.4
la solución propuesta:
main = print ((2:[n | n<-[3,5..], isOddPrime(n)]) !! 10000) where
isOddPrime(n) = _Prime(3) where
_Prime(y) = (y*y) > n || (rem n y > 0 && _Prime(y+2))
-- 100:0.02s 200:0.03s 300:0.04s 5000:3.02s 10000:8.60s
-- n^1.5
Timings mostrados son para código no compilado en GHCi (en una computadora portátil lenta). Empirical local orders of growth tomado como log(t2/t1)/log(n2/n1)
. Aún más rápido es probar por números primos, y no por números impares.
por cierto, las impresiones de códigos originales no prime la centésima primera, pero el número por encima de ella.
esto es sólo un estilo comentario. La convención estándar de Java es comenzar los nombres del método (función) con una minúscula. Es decir, "Prime" debería ser "principal". – nicerobot
La primera regla sobre el Proyecto Euler es: ¡No hablas del proyecto Euler! :) – Noctis