Хорошо, вот мой ответ, используя непрерывные дроби в одиночку.
Для начала давайте разберемся с терминологией.
Пусть X = p / q - неизвестная дробь.
Пусть Q (X, p / q) = знак (X - p / q) - функция запроса: если она равна 0мы угадали число, и если оно равно +/- 1, которое говорит нам знак нашей ошибки.
Условное обозначение для непрерывных дробей - это 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 ) ...)))
Мы будем следовать следующему алгоритму для 0
Инициализировать Y = 0 = [0], Z = 1 = [1], k = 0.
Внешний цикл : предварительные условия являются следующими:
Y и Z являются продолженными долями k + 1 членов, которые идентичны, за исключением последнего элемента, где они отличаются на 1, так что Y = [y 0 ;y 1 , y 2 , y 3 , ... y k ] и Z = [y 0 ;y 1 , y 2 , y 3 , ... y k + 1]
(- 1) k (YX) <0 <(-1) <sup>k (ZX) или, проще говоря, для четного k, Y
Увеличьте степень непрерывной дроби на 1 шаг без изменения значений чисел.В общем, если последние члены имеют вид y k и y k + 1, мы меняем его на [... y k , y k+ 1 = ∞] и [... y k , z k + 1 = 1].Теперь увеличьте k на 1.
Внутренние циклы : Это по сути то же самое, что и вопрос @ templatetypedef по поводу целых чисел.Мы делаем двухфазный двоичный поиск, чтобы приблизиться:
Внутренний цикл 1 : y k = ∞, z k = a, а X находится между Y и Z.
Последний член двойного Z: вычислить M = Z, но с m k = 2 * a =2 * z k .
Запросить неизвестное число: q = Q (X, M).
Еслиq = 0, у нас есть ответ и перейдем к шагу 17.
Если q и Q (X, Y) имеют противоположные знаки, это означает, что X находится между Y и M, поэтому установите Z = M и перейдите к шагу 5.
В противном случае установите Y = M и перейдите к следующему шагу:
Внутренний цикл 2. y k =b, z k = a, а X находится между Y и Z.
Если a и b отличаются на 1, поменяйте местами Y и Z, перейдите к шагу 2.
Выполнить бинарный поиск: вычислить M, где m k = floor ((a + b) / 2, и запрос q = Q (X, M).
Если q = 0, мы закончили и перейдем к шагу 17.
Если q и Q (X, Y) имеютпротивоположные знаки, это означает, что X находится между Y и M, поэтому установите Z = M и перейдите к шагу 11.
В противном случае, q и Q (X, Z) имеют противоположные знаки, этоозначает, что X находится между Z и M, поэтому установите Y = M и перейдите к шагу 11.
Готово: X = M.
Aконкретный пример для 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!
На каждом шаге вычисления M, диапазонинтервал уменьшается.Вероятно, довольно легко доказать (хотя я не буду этого делать), что интервал сокращается как минимум в 1 / sqrt (5) на каждом шаге, что показало бы, что этот алгоритм имеет O (log q) шагов.
Обратите внимание, что это можно объединить с первоначальным вопросом интервью templatetypedef и применить к любому рациональному числу p / q, а не только между 0 и 1, путем первого вычисления Q (X, 0),затем для целых положительных / отрицательных целых чисел, ограничивающих два последовательных целых числа, а затем использующих вышеупомянутый алгоритм для дробной части.
Когда у меня появится возможность, я опубликую программу на python, реализующую этот алгоритм.
edit : также обратите внимание, что вам не нужно вычислять непрерывную дробь на каждом шаге (которая будет O (k), есть частичные аппроксимации к непрерывным дробям, которые могут вычислить следующий шаг из предыдущий шаг в O (1).)
edit 2 : рекурсивное определение частичных приближений:
Если A k = [a 0 ; a 1 , a 2 , a 3 , ... a k ] = p k / q k , затем p k = a k p k-1 + p k-2 и q k = a k q k-1 + q k-2 . (Источник: Niven & Zuckerman, 4-е издание, теоремы 7.3-7.5. См. Также Википедия )
Пример: [0] = 0/1 = p 0 / q 0 , [0; 7] = 1/7 = p 1 / q 1 ; так [0; 7, 16] = (16 * 1 + 0) / (16 * 7 + 1) = 16/113 = p 2 / q 2 .
Это означает, что если две непрерывные дроби Y и Z имеют одинаковые термины, кроме последнего, и непрерывная дробь, исключающая последний член, равна p k-1 / q k-1 , тогда мы можем написать Y = (y k p k-1 + p k-2 ) / (y k q k-1 + q k-2 ) и Z = (z k p k-1 + p k -2 ) / (z k q k-1 + q k-2 ). Из этого следует показать, что | Y-Z | уменьшается по крайней мере в 1 / sqrt (5) на каждом меньшем интервале, производимом этим алгоритмом, но алгебра в данный момент, похоже, находится за пределами моего понимания. : - (
Вот моя программа на 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]
и пример вывода для 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
Поскольку Python с самого начала обрабатывает математику biginteger, а эта программа использует только целочисленную математику (кроме интервальных вычислений), она должна работать для произвольных рациональных чисел.
edit 3 : Схема доказательства того, что это O (log q), а не O (log ^ 2 q):
Сначала отметим, что до тех пор, пока не будет найдено рациональное число, число шагов n k для каждого нового члена с продолженной дробью будет точно 2b (a_k) -1, где b (a_k) количество битов, необходимых для представления a_k = ceil (log2 (a_k)): b (a_k) шагов, чтобы расширить «сеть» двоичного поиска, и b (a_k) -1 шагов, чтобы сузить его). Посмотрите пример выше, вы заметите, что число шагов всегда 1, 3, 7, 15 и т. Д.
Теперь мы можем использовать рекуррентное соотношение q k = a k q k-1 + q k-2 и индукцию доказать желаемый результат.
Давайте сформулируем это следующим образом: значение q после шагов N k = sum (n k ), необходимых для достижения k-го члена, имеет минимум: q> = A * 2 cN для некоторых фиксированных констант A, c. (чтобы инвертировать, мы получили бы, что число шагов N равно <= (1 / c) * log <sub>2 (q / A) = O (log q).)
Базовые шкафы:
- k = 0: q = 1, N = 0, поэтому q> = 2 N
- k = 1: для N = 2b-1 шагов q = a 1 > = 2 b-1 = 2 (N-1) / 2 = 2 N / 2 / sqrt (2).
Это означает, что A = 1, c = 1/2 может дать желаемые границы. В действительности, q может не удваивать каждый член (контрпример: [0; 1, 1, 1, 1, 1] имеет коэффициент роста phi = (1 + sqrt (5)) / 2), поэтому давайте использовать с = 1/4.
Индукция:
для термина k, q k = a k q k-1 + q k-2 . Опять же, для n k = 2b-1 шагов, необходимых для этого термина, a k > = 2 b-1 = 2 (n к * +1361 * -1) / 2 * * тысяча триста шестьдесят два. * * +1363
То есть 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 - трудная часть здесь в том, что если k = 1, то q может не сильно увеличиться для этого одного члена, и нам нужно использовать q k-2 , но это может быть намного меньше, чем q k-1 .