Okay, hier ist meine Antwort, wenn ich nur fortgesetzte Brüche verwende .
Lassen Sie uns zuerst eine Terminologie hier bekommen.
Sei X = p / q der unbekannte Bruch.
Sei Q (X, p / q) = Vorzeichen (X - p / q) die Abfragefunktion: Wenn es 0 ist, haben wir die Zahl erraten, und wenn es +/- 1 ist, sagt es uns das Vorzeichen unseres Fehlers .
Die herkömmliche Notation für fortgesetzte Brüche lautet 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 ) ...))
Wir folgen dem folgenden Algorithmus für 0 <p / q <1.
Initialisiere Y = 0 = [0], Z = 1 = [1], k = 0.
Äußere Schleife : Die Voraussetzungen sind:
Y und Z sind fortgesetzte Brüche von k + 1 Termen, die identisch sind, außer im letzten Element, wo sie sich um 1 unterscheiden, so dass Y = [y 0 ; y 1 , y 2 , y 3 , ... y k ] und Z = [y 0 ; y 1 , y 2 , y 3 , ... y k + 1]
(-1) k (YX) <0 <(-1) k (ZX) oder einfacher ausgedrückt für k gerade, Y <X <Z und für k ungerade, Z <X <Y.
Erweitern Sie den Grad der fortgesetzten Fraktion um 1 Schritt, ohne die Werte der Zahlen zu ändern. Wenn die letzten Terme y k und y k + 1 sind, ändern wir dies im Allgemeinen in [... y k , y k + 1 = ∞] und [... y k , z k + 1 = 1]. Erhöhen Sie nun k um 1.
Innere Schleifen : Dies entspricht im Wesentlichen der Interviewfrage von @ templatetypedef zu den ganzen Zahlen. Wir führen eine zweiphasige binäre Suche durch, um näher zu kommen:
Innere Schleife 1 : y k = ∞, z k = a und X liegt zwischen Y und Z.
Double Zs letzter Term: Berechne M = Z, aber mit m k = 2 * a = 2 * z k .
Fragen Sie die unbekannte Nummer ab: q = Q (X, M).
Wenn q = 0 ist, haben wir unsere Antwort und fahren mit Schritt 17 fort.
Wenn q und Q (X, Y) entgegengesetzte Vorzeichen haben, bedeutet dies, dass X zwischen Y und M liegt. Setzen Sie also Z = M und fahren Sie mit Schritt 5 fort.
Andernfalls setzen Sie Y = M und fahren Sie mit dem nächsten Schritt fort:
Innere Schleife 2. y k = b, z k = a und X liegt zwischen Y und Z.
Wenn sich a und b um 1 unterscheiden, tauschen Sie Y und Z aus und fahren Sie mit Schritt 2 fort.
Führen Sie eine binäre Suche durch: Berechnen Sie M mit m k = Etage ((a + b) / 2) und fragen Sie q = Q (X, M) ab.
Wenn q = 0 ist, sind wir fertig und fahren mit Schritt 17 fort.
Wenn q und Q (X, Y) entgegengesetzte Vorzeichen haben, bedeutet dies, dass X zwischen Y und M liegt. Setzen Sie also Z = M und fahren Sie mit Schritt 11 fort.
Andernfalls haben q und Q (X, Z) entgegengesetzte Vorzeichen. Dies bedeutet, dass X zwischen Z und M liegt. Setzen Sie also Y = M und fahren Sie mit Schritt 11 fort.
Fertig: X = M.
Ein konkretes Beispiel für 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!
Bei jedem Schritt der Berechnung von M verringert sich der Bereich des Intervalls. Es ist wahrscheinlich ziemlich einfach zu beweisen (obwohl ich das nicht tun werde), dass sich das Intervall bei jedem Schritt um einen Faktor von mindestens 1 / sqrt (5) verringert, was zeigen würde, dass dieser Algorithmus O (log q) Schritte ist.
Beachten Sie, dass dies mit der ursprünglichen Interviewfrage von templatetypedef kombiniert werden kann und auf jede rationale Zahl p / q angewendet werden kann , nicht nur zwischen 0 und 1, indem zuerst Q (X, 0) berechnet wird, dann für entweder positive / negative ganze Zahlen, die zwischen zwei aufeinanderfolgenden liegen Ganzzahlen und dann Verwendung des obigen Algorithmus für den Bruchteil.
Wenn ich das nächste Mal eine Chance habe, werde ich ein Python-Programm veröffentlichen, das diesen Algorithmus implementiert.
Bearbeiten : Beachten Sie auch, dass Sie nicht für jeden Schritt den fortgesetzten Bruch berechnen müssen (dies wäre O (k). Es gibt teilweise Annäherungen an fortgesetzte Brüche, mit denen der nächste Schritt aus dem vorherigen Schritt in O (1) berechnet werden kann. )
edit 2 : Rekursive Definition von partiellen Approximanten:
Wenn A k = [a 0 ; a 1 , a 2 , a 3 , ... a k ] = p k / q k , dann p k = a k p k-1 + p k-2 und q k = a k q k-1 + q k-2 . (Quelle: Niven & Zuckerman, 4. Auflage, Theoreme 7.3-7.5. Siehe auch Wikipedia )
Beispiel: [0] = 0/1 = p 0 / q 0 , [0; 7] = 1/7 = p 1 / q 1 ; also [0; 7, 16] = (16 · 1 + 0) / (16 · 7 + 1) = 16/113 = p 2 / q 2 .
Dies bedeutet, dass wenn zwei fortgesetzte Brüche Y und Z mit Ausnahme des letzten die gleichen Terme haben und der fortgesetzte Bruch ohne den letzten Term p k-1 / q k-1 ist , wir Y = (y k p k- schreiben können) 1 + p k-2 ) / (y k q k-1 + q k-2 ) und Z = (z k p k-1 + p k-2 ) / (z k q k-1 + q k-2) ). Daraus sollte gezeigt werden können, dass | YZ | nimmt in jedem kleineren Intervall, das von diesem Algorithmus erzeugt wird, um mindestens den Faktor 1 / sqrt (5) ab, aber die Algebra scheint mir im Moment ein Rätsel zu sein. :-(
Hier ist mein Python-Programm:
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]
und eine Beispielausgabe für 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
Da Python von Anfang an mit Biginteger-Mathematik umgeht und dieses Programm nur Integer-Mathematik verwendet (mit Ausnahme der Intervallberechnungen), sollte es für beliebige Rationals funktionieren.
edit 3 : Umriss des Beweises, dass dies O (log q) ist, nicht O (log ^ 2 q):
Beachten Sie zunächst, dass die Anzahl der Schritte n k für jeden neuen fortgesetzten Bruchterm genau 2b (a_k) -1 beträgt, bis die rationale Zahl gefunden ist, wobei b (a_k) die Anzahl der Bits ist, die zur Darstellung von a_k = lid (log2 (a_k) benötigt werden )): Es sind b (a_k) Schritte, um das "Netz" der binären Suche zu erweitern, und b (a_k) -1 Schritte, um es einzugrenzen). Im obigen Beispiel werden Sie feststellen, dass die Anzahl der Schritte immer 1, 3, 7, 15 usw. beträgt.
Jetzt können wir die Wiederholungsrelation q k = a k q k-1 + q k-2 und die Induktion verwenden, um das gewünschte Ergebnis zu beweisen.
Sagen wir es so: Der Wert von q nach den Schritten N k = Summe (n k ), die zum Erreichen des k-ten Terms erforderlich sind, hat ein Minimum: q> = A * 2 cN für einige feste Konstanten A, c. (Um zu invertieren, würden wir erhalten, dass die Anzahl der Schritte N <= (1 / c) * log 2 (q / A) = O (log q) ist.)
Basisfälle:
- k = 0: q = 1, N = 0, also q> = 2 N.
- k = 1: für N = 2b-1 Schritte ist q = a 1 > = 2 b-1 = 2 (N-1) / 2 = 2 N / 2 / sqrt (2).
Dies impliziert, dass A = 1, c = 1/2 die gewünschten Grenzen liefern könnte. In der Realität kann q nicht jeden Term verdoppeln (Gegenbeispiel: [0; 1, 1, 1, 1, 1] hat einen Wachstumsfaktor von phi = (1 + sqrt (5)) / 2), also verwenden wir c = 1 / 4.
Induktion:
für den Term k ist q k = a k q k-1 + q k-2 . Wiederum ist für die für diesen Term benötigten n k = 2b-1 Schritte a k > = 2 b-1 = 2 (n k -1) / 2 .
Also ist 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 / sqrt (2) * 2 n k / 4 .
Argh - der schwierige Teil hier ist, dass wenn a k = 1 ist, q für diesen einen Term möglicherweise nicht viel zunimmt und wir q k-2 verwenden müssen, aber das kann viel kleiner als q k-1 sein .