Ok, voici ma réponse en utilisant fractions continues seul.
Tout d'abord, il convient d'établir une certaine terminologie.
Soit X = p/q la fraction inconnue.
Soit Q(X,p/q) = sign(X - p/q) la fonction d'interrogation : si elle vaut 0, nous avons deviné le nombre, et si elle est +/- 1, elle nous indique le signe de notre erreur.
El notation conventionnelle pour les fractions continues est 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 ) ... )))
Nous suivrons l'algorithme suivant pour 0 < p/q < 1.
-
Initialiser Y = 0 = [ 0 ], Z = 1 = [ 1 ], k = 0.
-
Boucle extérieure : Le site conditions préalables sont que :
-
Y et Z sont des fractions continues de k+1 termes qui sont identiques sauf dans le dernier élément, où ils diffèrent de 1, de sorte que Y = [y 0 ; y 1 , y 2 , y 3 , ... y k ] et Z = [y 0 ; y 1 , y 2 , y 3 , ... y k + 1]
-
(-1) k (Y-X) < 0 < (-1) k (Z-X), ou en termes plus simples, pour k pair, Y < X < Z et pour k impair, Z < X < Y.
-
Prolongez le degré de la fraction continue d'un pas sans changer les valeurs des nombres. En général, si les derniers termes sont y k et y k + 1, nous changeons cela en [... y k , y k+1 \=∞] et [... y k , z k+1 \=1]. Augmentez maintenant k de 1.
-
Boucles intérieures : C'est essentiellement la même chose que la question d'entretien de @templatetypedef sur les entiers. Nous faisons une recherche binaire en deux phases pour nous rapprocher :
-
Boucle intérieure 1 : y k \= ∞, z k \= a, et X est entre Y et Z.
-
Le dernier terme de Double Z : Calculez M = Z mais avec m k \= 2*a = 2*z k .
-
Interroger le nombre inconnu : q = Q(X,M).
-
Si q = 0, nous avons notre réponse et passons à l'étape 17 .
-
Si q et Q(X,Y) ont des signes opposés, cela signifie que X se trouve entre Y et M, alors définissez Z = M et passez à l'étape 5.
-
Sinon, définir Y = M et passer à l'étape suivante :
-
Boucle intérieure 2. y k \= b, z k \= a, et X est entre Y et Z.
-
Si a et b diffèrent de 1, permutez Y et Z, passez à l'étape 2.
-
Effectuer une recherche binaire : calculer M où m k \= floor((a+b)/2, et la requête q = Q(X,M).
-
Si q = 0, nous avons terminé et passons à l'étape 17.
-
Si q et Q(X,Y) ont des signes opposés, cela signifie que X se trouve entre Y et M, alors définissez Z = M et passez à l'étape 11.
-
Sinon, q et Q(X,Z) ont des signes opposés, ce qui signifie que X est entre Z et M, alors définissez Y = M et passez à l'étape 11.
-
C'est fait : X = M.
Un exemple concret pour 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!
A chaque étape du calcul de M, l'étendue de l'intervalle se réduit. Il est probablement assez facile de prouver (mais je ne le ferai pas) que l'intervalle se réduit d'un facteur d'au moins 1/sqrt(5) à chaque étape, ce qui montrerait que cet algorithme est O(log q) étapes.
Notez que ceci peut être combiné avec la question d'entretien originale de templatetypedef et s'appliquer à tout nombre rationnel p/q, et pas seulement entre 0 et 1, en calculant d'abord Q(X,0), puis pour les entiers positifs/négatifs, en délimitant entre deux entiers consécutifs, et enfin en utilisant l'algorithme ci-dessus pour la partie fractionnaire.
Quand j'en aurai l'occasion, je posterai un programme python qui implémente cet algorithme.
modifier En outre, notez que vous n'avez pas à calculer la fraction continue à chaque étape (ce qui serait O(k), il existe des approximations partielles des fractions continues qui peuvent calculer l'étape suivante à partir de l'étape précédente en O(1)).
modifier 2 : Définition récursive des approximants partiels :
Si A k \= [a 0 ; a 1 , a 2 , a 3 , ... a k ] = p k /q k alors p k \= a k p k-1 + p k-2 et q k \= a k q k-1 + q k-2 . (Source : Niven & Zuckerman, 4th ed, Théorèmes 7.3-7.5. Voir aussi Wikipedia )
Exemple : [0] = 0/1 = p 0 /q 0 , [0 ; 7] = 1/7 = p 1 /q 1 ; donc [0 ; 7, 16] = (16*1+0)/(16*7+1) = 16/113 = p 2 /q 2 .
Cela signifie que si deux fractions continues Y et Z ont les mêmes termes sauf le dernier, et que la fraction continue excluant le dernier terme est p k-1 /q k-1 alors nous pouvons écrire Y = (y k p k-1 + p k-2 ) / (y k q k-1 + q k-2 ) et Z = (z k p k-1 + p k-2 ) / (z k q k-1 + q k-2 ). Il devrait être possible de montrer à partir de là que |Y-Z| diminue d'au moins un facteur de 1/sqrt(5) à chaque intervalle plus petit produit par cet algorithme, mais l'algèbre semble me dépasser pour le moment :-(
Voici mon programme 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]
et un exemple de sortie pour 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
Étant donné que Python gère dès le départ les calculs sur les grands nombres entiers et que ce programme n'utilise que des nombres entiers (sauf pour les calculs d'intervalles), il devrait fonctionner pour des rationnels arbitraires.
éditer 3 : Aperçu de la preuve que c'est O(log q), et non O(log^2 q) :
Notez d'abord que jusqu'à ce que le nombre rationnel soit trouvé, le nombre d'étapes n k pour chaque nouveau terme de fraction continue est exactement 2b(a_k)-1 où b(a_k) est le nombre de bits nécessaires pour représenter a_k = ceil(log2(a_k)) : il y a b(a_k) pas pour élargir le "filet" de la recherche binaire, et b(a_k)-1 pas pour le réduire). Dans l'exemple ci-dessus, vous noterez que le nombre de pas est toujours 1, 3, 7, 15, etc.
Nous pouvons maintenant utiliser la relation de récurrence q k \= a k q k-1 + q k-2 et l'induction pour prouver le résultat souhaité.
Disons-le ainsi : que la valeur de q après le N k \= somme(n k ) étapes nécessaires pour atteindre le kième terme a un minimum : q >= A*2 cN pour certaines constantes fixes A,c. (donc, pour inverser, nous obtiendrions que le nombre d'étapes N est <= (1/c) * log 2 (q/A) = O(log q).)
Cas de base :
- k=0 : q = 1, N = 0, donc q >= 2 N
- k=1 : pour N = 2b-1 étapes, q = a 1 >= 2 b-1 \= 2 (N-1)/2 \= 2 N/2 /sqrt(2).
Cela implique que A = 1, c = 1/2 pourraient fournir les limites souhaitées. En réalité, q peut no doubler chaque terme (contre-exemple : [0 ; 1, 1, 1, 1, 1, 1] a un facteur de croissance de phi = (1+sqrt(5))/2) donc utilisons c = 1/4.
Induction :
-
pour le terme k, q k \= a k q k-1 + q k-2 . Encore une fois, pour les n k \= 2b-1 étapes nécessaires pour ce terme, a k >= 2 b-1 \= 2 (n k -1)/2 .
Donc un 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 /sqrt(2)*2 n k /4 .
Argh -- la partie difficile ici est que si un k \= 1, q peut ne pas augmenter beaucoup pour ce seul terme, et nous devons utiliser q k-2 mais cela peut être beaucoup plus petit que q k-1 .