Bien, aquí está mi respuesta usando solo fracciones continuas .
Primero, obtengamos algo de terminología aquí.
Sea X = p / q la fracción desconocida.
Deje que Q (X, p / q) = sign (X - p / q) sea la función de consulta: si es 0, hemos adivinado el número, y si es +/- 1 que nos dice el signo de nuestro error .
La notación convencional para fracciones continuas es A = [a 0 ; a 1 , a 2 , a 3 , ... a k ]
= a 0 + 1 / (a 1 + 1 / (a 2 + 1 / (a 3 + 1 / (... + 1 / a k ) ...)))
Seguiremos el siguiente algoritmo para 0 <p / q <1.
Inicializar Y = 0 = [0], Z = 1 = [1], k = 0.
Bucle externo : las condiciones previas son las siguientes:
Y y Z son fracciones continuas de k + 1 términos que son idénticos excepto en el último elemento, donde difieren en 1, de modo que Y = [y 0 ; y 1 , y 2 , y 3 , ... y k ] y Z = [y 0 ; y 1 , y 2 , y 3 , ... y k + 1]
(-1) k (YX) <0 <(-1) k (ZX), o en términos más simples, para k par, Y <X <Z y para k impar, Z <X <Y.
Extiende el grado de la fracción continua en 1 paso sin cambiar los valores de los números. En general, si los últimos términos son y k y y k + 1, lo cambiamos a [... y k , y k + 1 = ∞] y [... y k , z k + 1 = 1]. Ahora aumente k en 1.
Bucles internos : esto es esencialmente lo mismo que la pregunta de la entrevista de @ templatetypedef sobre los números enteros. Hacemos una búsqueda binaria de dos fases para acercarnos:
Bucle interior 1 : y k = ∞, z k = a, y X está entre Y y Z.
Último término de Double Z: Calcule M = Z pero con m k = 2 * a = 2 * z k .
Consultar el número desconocido: q = Q (X, M).
Si q = 0, tenemos nuestra respuesta y vamos al paso 17.
Si q y Q (X, Y) tienen signos opuestos, significa que X está entre Y y M, así que configure Z = M y vaya al paso 5.
De lo contrario, configure Y = M y vaya al siguiente paso:
Bucle interior 2. y k = b, z k = a, y X está entre Y y Z.
Si ayb difieren en 1, intercambie Y y Z, vaya al paso 2.
Realice una búsqueda binaria: calcule M donde m k = piso ((a + b) / 2, y consulte q = Q (X, M).
Si q = 0, terminamos y vamos al paso 17.
Si q y Q (X, Y) tienen signos opuestos, significa que X está entre Y y M, así que configure Z = M y vaya al paso 11.
De lo contrario, q y Q (X, Z) tienen signos opuestos, significa que X está entre Z y M, así que configure Y = M y vaya al paso 11.
Hecho: X = M.
Un ejemplo concreto para X = 16/113 = 0.14159292
Y = 0 = [0], Z = 1 = [1], k = 0
k = 1:
Y = 0 = [0; ∞] < X, Z = 1 = [0; 1] > X, M = [0; 2] = 1/2 > X.
Y = 0 = [0; ∞], Z = 1/2 = [0; 2], M = [0; 4] = 1/4 > X.
Y = 0 = [0; ∞], Z = 1/4 = [0; 4], M = [0; 8] = 1/8 < X.
Y = 1/8 = [0; 8], Z = 1/4 = [0; 4], M = [0; 6] = 1/6 > X.
Y = 1/8 = [0; 8], Z = 1/6 = [0; 6], M = [0; 7] = 1/7 > X.
Y = 1/8 = [0; 8], Z = 1/7 = [0; 7]
--> the two last terms differ by one, so swap and repeat outer loop.
k = 2:
Y = 1/7 = [0; 7, ∞] > X, Z = 1/8 = [0; 7, 1] < X,
M = [0; 7, 2] = 2/15 < X
Y = 1/7 = [0; 7, ∞], Z = 2/15 = [0; 7, 2],
M = [0; 7, 4] = 4/29 < X
Y = 1/7 = [0; 7, ∞], Z = 4/29 = [0; 7, 4],
M = [0; 7, 8] = 8/57 < X
Y = 1/7 = [0; 7, ∞], Z = 8/57 = [0; 7, 8],
M = [0; 7, 16] = 16/113 = X
--> done!
En cada paso del cálculo de M, el rango del intervalo se reduce. Probablemente sea bastante fácil probar (aunque no haré esto) que el intervalo se reduce en un factor de al menos 1 / sqrt (5) en cada paso, lo que mostraría que este algoritmo es O (log q) pasos.
Tenga en cuenta que esto puede combinarse con la pregunta de entrevista original de templatetypedef y aplicarse a cualquier número racional p / q, no solo entre 0 y 1, calculando primero Q (X, 0), luego para enteros positivos / negativos, delimitando entre dos enteros, y luego usando el algoritmo anterior para la parte fraccionaria.
La próxima vez que tenga la oportunidad, publicaré un programa de Python que implemente este algoritmo.
editar : también, tenga en cuenta que no tiene que calcular la fracción continua en cada paso (que sería O (k), hay aproximaciones parciales a las fracciones continuas que pueden calcular el siguiente paso del paso anterior en O (1). )
edición 2 : Definición recursiva de aproximaciones parciales:
Si A k = [a 0 ; a 1 , a 2 , a 3 , ... a k ] = p k / q k , entonces p k = a k p k-1 + p k-2 , y q k = a k q k-1 + q k-2 . (Fuente: Niven & Zuckerman, 4th ed, Theorems 7.3-7.5. Ver también Wikipedia )
Ejemplo: [0] = 0/1 = p 0 / q 0 , [0; 7] = 1/7 = p 1 / q 1 ; entonces [0; 7, 16] = (16 * 1 + 0) / (16 * 7 + 1) = 16/113 = p 2 / q 2 .
Esto significa que si dos fracciones continuas Y y Z tienen los mismos términos excepto el último, y la fracción continua que excluye el último término es p k-1 / q k-1 , entonces podemos escribir Y = (y k p k- 1 + p k-2 ) / (y k q k-1 + q k-2 ) y Z = (z k p k-1 + p k-2 ) / (z k q k-1 + q k-2 ). Debería ser posible demostrar a partir de esto que | YZ | disminuye en al menos un factor de 1 / sqrt (5) en cada intervalo más pequeño producido por este algoritmo, pero el álgebra parece estar más allá de mí en este momento. :-(
Aquí está mi programa Python:
import math
# Return a function that returns Q(p0/q0,p/q)
# = sign(p0/q0-p/q) = sign(p0q-q0p)*sign(q0*q)
# If p/q < p0/q0, then Q() = 1; if p/q < p0/q0, then Q() = -1; otherwise Q()=0.
def makeQ(p0,q0):
def Q(p,q):
return cmp(q0*p,p0*q)*cmp(q0*q,0)
return Q
def strsign(s):
return '<' if s<0 else '>' if s>0 else '=='
def cfnext(p1,q1,p2,q2,a):
return [a*p1+p2,a*q1+q2]
def ratguess(Q, doprint, kmax):
# p2/q2 = p[k-2]/q[k-2]
p2 = 1
q2 = 0
# p1/q1 = p[k-1]/q[k-1]
p1 = 0
q1 = 1
k = 0
cf = [0]
done = False
while not done and (not kmax or k < kmax):
if doprint:
print 'p/q='+str(cf)+'='+str(p1)+'/'+str(q1)
# extend continued fraction
k = k + 1
[py,qy] = [p1,q1]
[pz,qz] = cfnext(p1,q1,p2,q2,1)
ay = None
az = 1
sy = Q(py,qy)
sz = Q(pz,qz)
while not done:
if doprint:
out = str(py)+'/'+str(qy)+' '+strsign(sy)+' X '
out += strsign(-sz)+' '+str(pz)+'/'+str(qz)
out += ', interval='+str(abs(1.0*py/qy-1.0*pz/qz))
if ay:
if (ay - az == 1):
[p0,q0,a0] = [pz,qz,az]
break
am = (ay+az)/2
else:
am = az * 2
[pm,qm] = cfnext(p1,q1,p2,q2,am)
sm = Q(pm,qm)
if doprint:
out = str(ay)+':'+str(am)+':'+str(az) + ' ' + out + '; M='+str(pm)+'/'+str(qm)+' '+strsign(sm)+' X '
print out
if (sm == 0):
[p0,q0,a0] = [pm,qm,am]
done = True
break
elif (sm == sy):
[py,qy,ay,sy] = [pm,qm,am,sm]
else:
[pz,qz,az,sz] = [pm,qm,am,sm]
[p2,q2] = [p1,q1]
[p1,q1] = [p0,q0]
cf += [a0]
print 'p/q='+str(cf)+'='+str(p1)+'/'+str(q1)
return [p1,q1]
y una salida de muestra para ratguess(makeQ(33102,113017), True, 20)
:
p/q=[0]=0/1
None:2:1 0/1 < X < 1/1, interval=1.0; M=1/2 > X
None:4:2 0/1 < X < 1/2, interval=0.5; M=1/4 < X
4:3:2 1/4 < X < 1/2, interval=0.25; M=1/3 > X
p/q=[0, 3]=1/3
None:2:1 1/3 > X > 1/4, interval=0.0833333333333; M=2/7 < X
None:4:2 1/3 > X > 2/7, interval=0.047619047619; M=4/13 > X
4:3:2 4/13 > X > 2/7, interval=0.021978021978; M=3/10 > X
p/q=[0, 3, 2]=2/7
None:2:1 2/7 < X < 3/10, interval=0.0142857142857; M=5/17 > X
None:4:2 2/7 < X < 5/17, interval=0.00840336134454; M=9/31 < X
4:3:2 9/31 < X < 5/17, interval=0.00379506641366; M=7/24 < X
p/q=[0, 3, 2, 2]=5/17
None:2:1 5/17 > X > 7/24, interval=0.00245098039216; M=12/41 < X
None:4:2 5/17 > X > 12/41, interval=0.00143472022956; M=22/75 > X
4:3:2 22/75 > X > 12/41, interval=0.000650406504065; M=17/58 > X
p/q=[0, 3, 2, 2, 2]=12/41
None:2:1 12/41 < X < 17/58, interval=0.000420521446594; M=29/99 > X
None:4:2 12/41 < X < 29/99, interval=0.000246366100025; M=53/181 < X
4:3:2 53/181 < X < 29/99, interval=0.000111613371282; M=41/140 < X
p/q=[0, 3, 2, 2, 2, 2]=29/99
None:2:1 29/99 > X > 41/140, interval=7.21500721501e-05; M=70/239 < X
None:4:2 29/99 > X > 70/239, interval=4.226364059e-05; M=128/437 > X
4:3:2 128/437 > X > 70/239, interval=1.91492009996e-05; M=99/338 > X
p/q=[0, 3, 2, 2, 2, 2, 2]=70/239
None:2:1 70/239 < X < 99/338, interval=1.23789953207e-05; M=169/577 > X
None:4:2 70/239 < X < 169/577, interval=7.2514738621e-06; M=309/1055 < X
4:3:2 309/1055 < X < 169/577, interval=3.28550190148e-06; M=239/816 < X
p/q=[0, 3, 2, 2, 2, 2, 2, 2]=169/577
None:2:1 169/577 > X > 239/816, interval=2.12389981991e-06; M=408/1393 < X
None:4:2 169/577 > X > 408/1393, interval=1.24415093544e-06; M=746/2547 < X
None:8:4 169/577 > X > 746/2547, interval=6.80448470014e-07; M=1422/4855 < X
None:16:8 169/577 > X > 1422/4855, interval=3.56972657711e-07; M=2774/9471 > X
16:12:8 2774/9471 > X > 1422/4855, interval=1.73982239227e-07; M=2098/7163 > X
12:10:8 2098/7163 > X > 1422/4855, interval=1.15020646951e-07; M=1760/6009 > X
10:9:8 1760/6009 > X > 1422/4855, interval=6.85549088053e-08; M=1591/5432 < X
p/q=[0, 3, 2, 2, 2, 2, 2, 2, 9]=1591/5432
None:2:1 1591/5432 < X < 1760/6009, interval=3.06364213998e-08; M=3351/11441 < X
p/q=[0, 3, 2, 2, 2, 2, 2, 2, 9, 1]=1760/6009
None:2:1 1760/6009 > X > 3351/11441, interval=1.45456726663e-08; M=5111/17450 < X
None:4:2 1760/6009 > X > 5111/17450, interval=9.53679318849e-09; M=8631/29468 < X
None:8:4 1760/6009 > X > 8631/29468, interval=5.6473816179e-09; M=15671/53504 < X
None:16:8 1760/6009 > X > 15671/53504, interval=3.11036635336e-09; M=29751/101576 > X
16:12:8 29751/101576 > X > 15671/53504, interval=1.47201634215e-09; M=22711/77540 > X
12:10:8 22711/77540 > X > 15671/53504, interval=9.64157420569e-10; M=19191/65522 > X
10:9:8 19191/65522 > X > 15671/53504, interval=5.70501257346e-10; M=17431/59513 > X
p/q=[0, 3, 2, 2, 2, 2, 2, 2, 9, 1, 8]=15671/53504
None:2:1 15671/53504 < X < 17431/59513, interval=3.14052228667e-10; M=33102/113017 == X
Dado que Python maneja matemáticas de números enteros desde el principio, y este programa solo usa matemáticas de números enteros (excepto para los cálculos de intervalo), debería funcionar para racionales arbitrarias.
edición 3 : Esquema de la prueba de que esto es O (log q), no O (log ^ 2 q):
Primero tenga en cuenta que hasta que se encuentre el número racional, el # de pasos n k para cada nuevo término de fracción continua es exactamente 2b (a_k) -1 donde b (a_k) es el # de bits necesarios para representar a_k = ceil (log2 (a_k )): son b (a_k) pasos para ampliar la "red" de la búsqueda binaria, y b (a_k) -1 pasos para reducirla). Vea el ejemplo anterior, observará que el número de pasos es siempre 1, 3, 7, 15, etc.
Ahora podemos usar la relación de recurrencia q k = a k q k-1 + q k-2 y la inducción para demostrar el resultado deseado.
Digámoslo de esta manera: que el valor de q después de los N k = suma (n k ) pasos requeridos para alcanzar el k-ésimo término tiene un mínimo: q> = A * 2 cN para algunas constantes fijas A, c. (para invertir, obtendríamos que el # de pasos N es <= (1 / c) * log 2 (q / A) = O (log q).)
Casos base:
- k = 0: q = 1, N = 0, entonces q> = 2 N
- k = 1: para N = 2b-1 pasos, q = a 1 > = 2 b-1 = 2 (N-1) / 2 = 2 N / 2 / sqrt (2).
Esto implica que A = 1, c = 1/2 podría proporcionar los límites deseados. En realidad, q no puede duplicar cada término (contraejemplo: [0; 1, 1, 1, 1, 1] tiene un factor de crecimiento de phi = (1 + sqrt (5)) / 2) así que usemos c = 1 / 4.
Inducción:
para el término k, q k = a k q k-1 + q k-2 . Nuevamente, para los n k = 2b-1 pasos necesarios para este término, a k > = 2 b-1 = 2 (n k -1) / 2 .
Entonces a k q k-1 > = 2 (N k -1) / 2 * q k-1 > = 2 (n k -1) / 2 * A * 2 N k-1 /4 = A * 2 N k / 4 / raíz cuadrada (2) * 2 n k / 4 .
Argh, la parte difícil aquí es que si a k = 1, es posible que q no aumente mucho para ese término, y necesitamos usar q k-2 pero eso puede ser mucho más pequeño que q k-1 .