¿Hay algún algoritmo subcuadrático conocido para calcular el piso de la raíz cuadrada de un n
entero de bits?
El algoritmo ingenuo sería algo así como
def sqrt(x):
r = 0
i = x.bit_length() // 2
while i >= 0:
inc = (r << (i+1)) + (1 << (i*2))
if inc <= x:
x -= inc
r += 1 << i
i -= 1
return r
Esto requiere O(n)
iteraciones, cada una con adiciones que son O(n)
tiempo, por lo que es O(n^2)
tiempo en general. ¿Hay algo más rápido? Sé que para el caso de la multiplicación hay algoritmos especiales que funcionan mejor que el tiempo cuadrático, pero no puedo encontrar nada para las raíces cuadradas.
algorithms
numerical-algorithms
Antimonio
fuente
fuente
Respuestas:
Puede usar el método de Newton o cualquiera de varios otros métodos para encontrar aproximaciones a las raíces del polinomio .p ( x ) = x2- c
La tasa de convergencia para el método de Newton será cuadrática, lo que significa que el número de bits correctos se duplica en cada iteración. Esto significa que son suficientes iteraciones del método de Newton.O ( lgn )
Cada iteración del método de Newton calcula
La complejidad de bits de la multiplicación es , para multiplicar dos enteros de bits (ignorando los factores ). La complejidad de bits para la división (a bits de precisión) es la misma. Por lo tanto, cada iteración se puede calcular en operaciones . Multiplicando por iteraciones, encontramos que el tiempo de ejecución general para calcular la raíz cuadrada con bits de precisión es . Esto es subcuadrático.blglgbb O (nlgn)O(lgn)n O (n(lgn)2)O ( b lgb ) si lglgsi si O ( n lgn ) O ( lgn ) norte O ( n ( lgn )2)
Creo que un análisis más cuidadoso muestra que esto se puede mejorar al tiempo de ejecución de (al tener en cuenta que solo necesitamos conocer cada con una de aproximadamente bits, en lugar de bits de precisión). Sin embargo, incluso el análisis más básico ya muestra un tiempo de ejecución que es claramente subcuadrático.xjjnO ( n lgn ) Xj j norte
fuente
Uno de los problemas con el método de Newton es que requiere una operación de división en cada iteración, que es la operación entera básica más lenta.
Sin embargo, el método de Newton para la raíz cuadrada recíproca no. Si es el número para el que desea encontrar , repita:1x 1x√
Esto a menudo se expresa como:
d i = 1 - w i x r i + 1 = r i + r i d i
Eso son tres operaciones de multiplicación. La división por dos se puede implementar como un desplazamiento a la derecha.
Ahora el problema es quer no es un número entero. Sin embargo, puede manipularlo como tal mediante la implementación de punto flotante manualmente, y haciendo un montón de operaciones de desplazamiento para compensar cuando sea apropiado.
Primero, cambiemos la escala de :x
donde nos gustaría que sea mayor que, pero cercano a, . Si ejecutamos el algoritmo anterior en lugar de , encontramos . Entonces, . 1 x ′ x r = 1x′ 1 x′ x √r=1x√′ x−−√=2erx′
Ahora dividamos en una mantisa y exponente:r
donde es un número entero. Intuitivamente, representa la precisión de la respuesta.r′i ei
Sabemos que el método de Newton duplica aproximadamente el número de dígitos significativos precisos. Entonces podemos elegir:
Con un poco de manipulación, encontramos:
En cada iteración:
Como ejemplo, intentemos calcular la raíz cuadrada de . Sabemos que la respuesta es . La raíz cuadrada recíproca es , por lo que estableceremos (esta es la escala del problema) y para nuestra conjetura inicial elegiremos y . (Es decir, elegimos para nuestra estimación inicial de ).x=263 2312–√ 12√2−31 e=31 r′0=3 e0=2 34 12√
Entonces:
Podemos determinar cuándo dejar de iterar comparando con ; Si he calculado correctamente, debería ser lo suficientemente bueno. Sin embargo, nos detendremos aquí y encontraremos:ei e ei>2e
La raíz cuadrada entera correcta es , por lo que estamos bastante cerca. Podríamos hacer otra iteración o una iteración final optimizada que no duplique . Los detalles se dejan como ejercicio.3037000499 ei
Para analizar la complejidad de este método, tenga en cuenta que multiplicar dos enteros de bits requiere operaciones . Sin embargo, hemos organizado las cosas para que . Por lo que la multiplicación para calcular multiplica dos números de bits para producir un número de bits, y los otros dos multiplicaciones multiplicar dos números -bit para producir una -número de bit.b O(blogb) r′i<2ei wi ei ei+1 ei+1 2ei+1
En cada caso, el número de operaciones por iteración es , y se requieren iteraciones . La multiplicación final está en el orden de las operaciones . Entonces, la complejidad general son las operaciones , que es subcuadrática en el número de bits en . Eso cumple todos los requisitos.O ( log e ) O ( 2 e log 2 e ) O ( e log 2 e ) xO(eilogei) O(loge) O(2elog2e) O(elog2e) x
Sin embargo, este análisis esconde un principio importante que todos los que trabajan con enteros grandes deben tener en cuenta: debido a que la multiplicación es superlineal en el número de bits, cualquier operación de multiplicación solo debe realizarse en enteros que tengan aproximadamente la magnitud de la precisión actual (y , Podría agregar, deberías intentar multiplicar números que tengan un orden de magnitud similar). Usar enteros más grandes que eso es una pérdida de esfuerzo. Los factores constantes importan, y para los enteros grandes, importan mucho.
Como observación final, dos de las multiplicaciones son de la forma . Claramente es un desperdicio calcular todos los bits de solo para tirar de ellos con un desplazamiento a la derecha. La implementación de un método de multiplicación inteligente que tenga esto en cuenta también se deja como un ejercicio. abcab2c ab c
fuente