Trò chơi "đoán số" cho các số hữu tỉ tùy ý?


77

Tôi đã từng nhận được những điều sau đây như một câu hỏi phỏng vấn:

Tôi đang nghĩ đến một số nguyên dương n. Đưa ra một thuật toán có thể đoán nó trong các truy vấn O (lg n). Mỗi truy vấn là một số lựa chọn của bạn và tôi sẽ trả lời "thấp hơn", "cao hơn" hoặc "đúng".

Vấn đề này có thể được giải quyết bằng tìm kiếm nhị phân đã sửa đổi, trong đó bạn liệt kê các lũy thừa của hai cho đến khi bạn tìm thấy một lũy thừa vượt quá n, sau đó chạy tìm kiếm nhị phân tiêu chuẩn trên phạm vi đó. Điều tôi nghĩ rất thú vị về điều này là bạn có thể tìm kiếm một không gian vô hạn cho một số cụ thể nhanh hơn so với chỉ brute-force.

Tuy nhiên, câu hỏi tôi có là một sửa đổi nhỏ của vấn đề này. Thay vì chọn một số nguyên dương, giả sử rằng tôi chọn một số hữu tỉ tùy ý giữa số 0 và một. Câu hỏi của tôi là: bạn có thể sử dụng thuật toán nào để xác định hiệu quả nhất số hữu tỉ mà tôi đã chọn?

Ngay bây giờ, giải pháp tốt nhất mà tôi có có thể tìm p / q trong thời gian tối đa là O (q) bằng cách đi ngầm cây Stern-Brocot , một cây tìm kiếm nhị phân trên tất cả các hợp lý. Tuy nhiên, tôi đã hy vọng có được thời gian chạy gần hơn với thời gian chạy mà chúng ta nhận được cho trường hợp số nguyên, có thể là một cái gì đó như O (lg (p + q)) hoặc O (lg pq). Có ai biết cách để có được loại thời gian chạy này không?

Ban đầu tôi đã xem xét sử dụng tìm kiếm nhị phân tiêu chuẩn của khoảng [0, 1], nhưng điều này sẽ chỉ tìm thấy các số hữu tỉ có biểu diễn nhị phân không lặp lại, thiếu gần như tất cả các số hữu tỉ. Tôi cũng đã nghĩ đến việc sử dụng một số cách khác để liệt kê các số hợp lý, nhưng dường như tôi không thể tìm ra cách nào để tìm kiếm không gian này chỉ với các phép so sánh lớn hơn / bằng / nhỏ hơn.


3
Ừm, bạn có nhận ra rằng không có một số giới hạn thì điều này là không thể, vì có vô hạn số hữu tỉ trong bất kỳ phạm vi nào? Bạn cũng không thể thực sự tìm kiếm một số nguyên không giới hạn; giả sử số đó là một số ngẫu nhiên có 10 ^ 1000 chữ số? "100" - "cao hơn". "1000" - "cao hơn". "Một triệu" - "cao hơn". "Một nghìn tỷ?" "Cao hơn." "Một cái googol ?" "Cao hơn!"
Tom Zych

16
@Tom - Cho bất kỳ số nào, (chẳng hạn như 10 ^ 1000), thuật toán sẽ tìm nó trong một khoảng thời gian hữu hạn (ngay cả khi đó là một thời gian rất dài). Điều này khác với việc nói rằng thuật toán có thể đoán bất kỳ số nào trong vòng t bước (đối với một số giá trị cố định của t), nhưng không ai đưa ra khẳng định này.
Seth

6
@Tom Zych- nếu bạn chọn bất kỳ số nguyên hữu hạn nào, cuối cùng tôi có thể tìm thấy nó bằng cách nhân đôi liên tục. Nó có thể mất một khoảng thời gian vô lý, nhưng tôi vẫn có thể làm điều đó trong thời gian tỷ lệ thuận với logarit của số của bạn. Trong điều này, tôi giả định rằng người trả lời các câu hỏi đại diện một cách trung thực cho số lượng và không chỉ né tránh bằng cách trả lời theo cách không bao giờ kết thúc.
templatetypedef,

Thuật toán thú vị. Tất cả rationals với mẫu N nằm trước (hoặc in) mức N của cây, vì vậy O (q) là rõ ràng càng tốt
Tiến sĩ Belisarius

4
@Everyone: Tôi chỉ muốn nói, đây là một câu hỏi thú vị với một số câu trả lời và thảo luận gọn gàng. Tôi rất vui.
Seth

Câu trả lời:


49

Được rồi, đây là câu trả lời của tôi chỉ sử dụng phân số tiếp tục .

Đầu tiên chúng ta hãy lấy một số thuật ngữ ở đây.

Gọi X = p / q là phân số chưa biết.

Gọi Q (X, p / q) = sign (X - p / q) là hàm truy vấn: nếu nó là 0, chúng tôi đã đoán số và nếu nó +/- 1 cho chúng tôi biết dấu hiệu lỗi của chúng tôi .

Các ký hiệu thông thường để tiếp tục phân số là 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 ) ...)))


Chúng tôi sẽ làm theo thuật toán sau cho 0 <p / q <1.

  1. Khởi tạo Y = 0 = [0], Z = 1 = [1], k = 0.

  2. Vòng lặp bên ngoài : Điều kiện tiên quyết là:

    • Y và Z là các phân số liên tiếp của k + 1 số hạng giống hệt nhau ngoại trừ phần tử cuối cùng, chúng khác nhau 1, sao cho Y = [y 0 ; y 1 , y 2 , y 3 , ... y k ] và Z = [y 0 ; y 1 , y 2 , y 3 , ... y k + 1]

    • (-1) k (YX) <0 <(-1) k (ZX), hoặc đơn giản hơn, với k chẵn, Y <X <Z và k lẻ, Z <X <Y.

  3. Mở rộng bậc của phân số tiếp tục thêm 1 bước mà không thay đổi giá trị của các số. Nói chung, nếu các số hạng cuối cùng là y k và y k + 1, chúng ta đổi thành [... y k , y k + 1 = ∞] và [... y k , z k + 1 = 1]. Bây giờ tăng k thêm 1.

  4. Vòng lặp bên trong : Điều này về cơ bản giống với câu hỏi phỏng vấn của @ templatetypedef về các số nguyên. Chúng tôi thực hiện tìm kiếm nhị phân hai giai đoạn để đến gần hơn:

  5. Vòng lặp bên trong 1 : y k = ∞, z k = a, và X nằm giữa Y và Z.

  6. Nhân đôi số hạng cuối của Z: Tính M = Z nhưng với m k = 2 * a = 2 * z k .

  7. Truy vấn số chưa biết: q = Q (X, M).

  8. Nếu q = 0, chúng ta có câu trả lời và chuyển sang bước 17.

  9. Nếu q và Q (X, Y) có dấu trái dấu, nghĩa là X nằm giữa Y và M, do đó đặt Z = M và chuyển sang bước 5.

  10. Nếu không, hãy đặt Y = M và chuyển sang bước tiếp theo:

  11. Vòng lặp bên trong 2. y k = b, z k = a, và X nằm giữa Y và Z.

  12. Nếu a và b khác 1, hoán đổi Y và Z, chuyển sang bước 2.

  13. Thực hiện tìm kiếm nhị phân: tính M trong đó m k = floor ((a + b) / 2 và truy vấn q = Q (X, M).

  14. Nếu q = 0, chúng ta đã hoàn thành và chuyển sang bước 17.

  15. Nếu q và Q (X, Y) có dấu trái dấu, nghĩa là X nằm giữa Y và M, vì vậy đặt Z = M và chuyển sang bước 11.

  16. Nếu không, q và Q (X, Z) có dấu hiệu trái dấu, nghĩa là X nằm giữa Z và M, vì vậy hãy đặt Y = M và chuyển sang bước 11.

  17. Thực hiện: X = M.

Một ví dụ cụ thể cho X = 16/113 = 0,14159292

Y = 0 = [0], Z = 1 = [1], k = 0

k = 1:
Y = 0 = [0; &#8734;] < X, Z = 1 = [0; 1] > X, M = [0; 2] = 1/2 > X.
Y = 0 = [0; &#8734;], Z = 1/2 = [0; 2], M = [0; 4] = 1/4 > X.
Y = 0 = [0; &#8734;], 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, &#8734;] > X, Z = 1/8 = [0; 7, 1] < X,
    M = [0; 7, 2] = 2/15 < X
Y = 1/7 = [0; 7, &#8734;], Z = 2/15 = [0; 7, 2],
    M = [0; 7, 4] = 4/29 < X
Y = 1/7 = [0; 7, &#8734;], Z = 4/29 = [0; 7, 4], 
    M = [0; 7, 8] = 8/57 < X
Y = 1/7 = [0; 7, &#8734;], Z = 8/57 = [0; 7, 8],
    M = [0; 7, 16] = 16/113 = X 
    --> done!

Tại mỗi bước tính toán M, phạm vi của khoảng giảm. Có lẽ khá dễ dàng để chứng minh (mặc dù tôi sẽ không làm điều này) rằng khoảng thời gian giảm đi một hệ số ít nhất là 1 / sqrt (5) ở mỗi bước, điều này sẽ cho thấy rằng thuật toán này là O (log q) bước.

Lưu ý rằng điều này có thể được kết hợp với câu hỏi phỏng vấn ban đầu của templatetypedef và áp dụng cho bất kỳ số hữu tỉ p / q nào, không chỉ giữa 0 và 1, bằng cách tính toán đầu tiên Q (X, 0), sau đó cho cả hai số nguyên dương / âm, giới hạn giữa hai liên tiếp số nguyên, và sau đó sử dụng thuật toán trên cho phần phân số.

Khi có cơ hội tiếp theo, tôi sẽ đăng một chương trình python thực hiện thuật toán này.

chỉnh sửa : ngoài ra, lưu ý rằng bạn không cần phải tính phân số tiếp theo mỗi bước (sẽ là O (k), có những giá trị gần đúng một phần cho phân số tiếp tục có thể tính bước tiếp theo từ bước trước đó trong O (1). )

chỉnh sửa 2 : Định nghĩa đệ quy của xấp xỉ từng phần:

Nếu A k = [a 0 ; a 1 , a 2 , a 3 , ... a k ] = p k / q k , thì p k = a k p k-1 + p k-2 , và q k = a k q k-1 + q k-2 . (Nguồn: Niven & Zuckerman, xuất bản lần thứ 4, Định lý 7.3-7.5. Xem thêm Wikipedia )

Ví dụ: [0] = 0/1 = p 0 / q 0 , [0; 7] = 1/7 = p 1 / q 1 ; vì vậy [0; 7, 16] = (16 * 1 + 0) / (16 * 7 + 1) = 16/113 = p 2 / q 2 .

Điều này có nghĩa là nếu hai phân số tiếp tục Y và Z có cùng số hạng ngoại trừ số cuối cùng và phân số tiếp tục không bao gồm số hạng cuối cùng là p k-1 / q k-1 , thì chúng ta có thể viết Y = (y k p k- 1 + p k-2 ) / (y k q k-1 + q k-2 ) và Z = (z k p k-1 + p k-2 ) / (z k q k-1 + q k-2 ). Từ đó có thể cho thấy rằng | YZ | giảm ít nhất một hệ số là 1 / sqrt (5) tại mỗi khoảng thời gian nhỏ hơn được tạo ra bởi thuật toán này, nhưng đại số dường như vượt xa tôi vào lúc này. :-(

Đây là chương trình Python của tôi:

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]

và đầu ra mẫu cho 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

Vì Python xử lý phép toán biginteger ngay từ đầu và chương trình này chỉ sử dụng phép toán số nguyên (ngoại trừ các phép tính khoảng), nên nó sẽ hoạt động với các số hữu tỉ tùy ý.


sửa 3 : Phác thảo chứng minh rằng đây là O (log q), không phải O (log ^ 2 q):

Trước tiên, lưu ý rằng cho đến khi tìm thấy số hữu tỉ, số bước n k cho mỗi số hạng phân số tiếp tục mới chính xác là 2b (a_k) -1 trong đó b (a_k) là số bit cần thiết để biểu diễn a_k = ceil (log2 (a_k )): đó là b (a_k) bước để mở rộng "mạng lưới" của tìm kiếm nhị phân và b (a_k) -1 bước để thu hẹp nó). Xem ví dụ trên, bạn sẽ lưu ý rằng số bước luôn là 1, 3, 7, 15, v.v.

Bây giờ chúng ta có thể sử dụng quan hệ lặp lại q k = a k q k-1 + q k-2 và quy nạp để chứng minh kết quả mong muốn.

Hãy phát biểu theo cách này: giá trị của q sau N k = sum (n k ) bước cần thiết để đạt đến số hạng thứ k có giá trị nhỏ nhất: q> = A * 2 cN đối với một số hằng số cố định A, c. (vì vậy để đảo ngược, chúng ta sẽ nhận được rằng số bước N là <= (1 / c) * log 2 (q / A) = O (log q).)

Các trường hợp cơ bản:

  • k = 0: q = 1, N = 0, do đó q> = 2 N
  • k = 1: với N = 2b-1 bước, q = a 1 > = 2 b-1 = 2 (N-1) / 2 = 2 N / 2 / sqrt (2).

Điều này ngụ ý A = 1, c = 1/2 có thể cung cấp giới hạn mong muốn. Trong thực tế, q có thể không nhân đôi mỗi số hạng (ví dụ: [0; 1, 1, 1, 1, 1] có hệ số tăng trưởng là phi = (1 + sqrt (5)) / 2) vì vậy hãy sử dụng c = 1 / 4.

Hướng dẫn:

  • đối với số hạng k, q k = a k q k-1 + q k-2 . Một lần nữa, với n k = 2b-1 bước cần thiết cho số hạng này, a k > = 2 b-1 = 2 (n k -1) / 2 .

    Vậy 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 - phần khó khăn ở đây là nếu k = 1, q có thể không tăng nhiều cho một số hạng đó, và chúng ta cần sử dụng q k-2 nhưng nó có thể nhỏ hơn nhiều so với q k-1 .


Vì vậy, điều này trông thực sự tuyệt vời, nhưng tôi không nghĩ nó là O (lg q). Bất kỳ lần lặp riêng lẻ nào của vòng lặp bên trong đều chạy theo bước O (lg q) khi bạn sử dụng tìm kiếm nhị phân đã sửa đổi để khôi phục số tiếp theo của phân số tiếp tục, nhưng hãy nhớ rằng có O (lg q) lần lặp của vòng lặp đó vì có ( trong trường hợp xấu nhất) O (lg q) các số trong phân số từng phần. Điều đó khiến tôi nghĩ rằng đây là thời gian O (lg ^ 2 q). Tuy nhiên, đây vẫn là một giải pháp tuyệt vời cho vấn đề và cho dù đó là thời gian O (lg q) hay O (lg ^ 2 q), nó vẫn tốt hơn theo cấp số nhân so với những gì tôi có trước đây.
templatetypedef,

Tôi biết nó trông giống như O (lg ^ 2 q) vì hai vòng lặp, nhưng điều đó có lẽ là bảo thủ. Tôi sẽ cố gắng chứng minh điều đó.
Jason S

+1: Chưa kiểm tra chi tiết, nhưng tôi tin rằng cách tiếp cận CF sẽ hoạt động.

Bạn cần cố gắng và thể hiện điều đó | YZ | giảm về mặt hình học. Như typedeftemplate đã đề cập trong câu trả lời của mình, có các số hạng O (log q) trong CF, mỗi số có kích thước tối đa là q. Vì vậy, nếu bạn thực hiện các bước O (log q) để tăng 'độ' của CF của bạn lên 1, bạn thực hiện tổng số bước O (log ^ 2 q).

Không, bạn không thể đánh giá nó là O (log ^ 2 q) vì hai vòng lặp; đó là sự bảo thủ quá mức. Nếu bạn thực hiện các bước O (log q) để tăng số hạng của phân số tiếp tục, thì số hạng của phân số đó rất lớn và khoảng sẽ rất nhỏ. Mỗi lần lặp lại của vòng lặp bên trong cũng làm giảm khoảng thời gian, không chỉ tăng độ dài phân số tiếp tục.
Jason S

6

Hãy lấy các số hữu tỉ, ở dạng rút gọn, và viết chúng theo thứ tự trước tiên là mẫu số, sau đó đến tử số.

1/2, 1/3, 2/3, 1/4, 3/4, 1/5, 2/5, 3/5, 4/5, 1/6, 5/6, ...

Dự đoán đầu tiên của chúng tôi sẽ là 1/2. Sau đó, chúng tôi sẽ đi dọc theo danh sách cho đến khi chúng tôi có 3 trong phạm vi của chúng tôi. Sau đó, chúng ta sẽ thực hiện 2 lần đoán để tìm kiếm danh sách đó. Sau đó, chúng tôi sẽ đi dọc theo danh sách cho đến khi chúng tôi có 7 trong phạm vi còn lại của chúng tôi. Sau đó, chúng tôi sẽ thực hiện 3 lần đoán để tìm kiếm danh sách đó. Và như thế.

Trong ncác bước, chúng tôi sẽ đề cập đến các khả năng đầu tiên , theo thứ tự mức độ hiệu quả mà bạn đang tìm kiếm.2O(n)

Cập nhật: Mọi người không hiểu lý do đằng sau điều này. Lý do rất đơn giản. Chúng tôi biết cách đi một cây nhị phân một cách hiệu quả. Có những phân số với mẫu số lớn nhất . Do đó, chúng tôi có thể tìm kiếm tới bất kỳ kích thước mẫu số cụ thể nào trong các bước. Vấn đề là chúng ta có vô số các số hữu tỉ có thể tìm kiếm. Vì vậy, chúng ta không thể chỉ xếp tất cả chúng, đặt hàng và bắt đầu tìm kiếm.O(n2)nO(2*log(n)) = O(log(n))

Vì vậy, ý tưởng của tôi là xếp hàng một vài, tìm kiếm, xếp hàng nhiều hơn, tìm kiếm, v.v. Mỗi lần chúng tôi xếp hàng nhiều hơn, chúng tôi xếp hàng gấp đôi những gì chúng tôi đã làm lần trước. Vì vậy, chúng tôi cần thêm một lần đoán so với lần trước. Do đó, đường chuyền đầu tiên của chúng ta sử dụng 1 lần đoán để vượt qua 1 lượt hợp lý có thể. Thứ hai của chúng tôi sử dụng 2 dự đoán để vượt qua 3 số hợp lý có thể. Thứ ba của chúng tôi sử dụng 3 dự đoán để vượt qua 7 số hợp lý có thể. Và kthứ của chúng tôi sử dụng các kphỏng đoán để vượt qua các lý do có thể có. Đối với bất kỳ lý trí cụ thể nào , cuối cùng nó sẽ kết thúc việc đưa hợp lý đó vào một danh sách khá lớn mà nó biết cách thực hiện tìm kiếm nhị phân một cách hiệu quả.2k-1m/n

Nếu chúng tôi đã tìm kiếm nhị phân, sau đó bỏ qua tất cả mọi thứ chúng tôi đã học được khi chúng ta chụp được nhiều rationals, sau đó chúng tôi muốn đưa tất cả các rationals lên đến và bao gồm m/ntrong O(log(n))đèo. (Đó là bởi vì đến thời điểm đó, chúng ta sẽ vượt qua với đủ lý do để bao gồm mọi lý do cho đến và bao gồm m/n.) Nhưng mỗi lần vượt qua cần nhiều lần đoán hơn, vì vậy đó sẽ là những lần đoán.O(log(n)2)

Tuy nhiên chúng tôi thực sự làm tốt hơn thế rất nhiều. Với dự đoán đầu tiên của chúng tôi, chúng tôi loại bỏ một nửa lý do trong danh sách của chúng tôi là quá lớn hoặc quá nhỏ. Hai dự đoán tiếp theo của chúng tôi không hoàn toàn cắt không gian thành các phần tư, nhưng chúng không đi quá xa nó. 3 dự đoán tiếp theo của chúng tôi một lần nữa không hoàn toàn cắt khoảng trống thành phần tám, nhưng chúng không đi quá xa so với nó. Và như thế. Khi bạn tổng hợp nó lại với nhau, tôi tin rằng kết quả là bạn tìm thấy m/ntrong O(log(n))các bước. Mặc dù tôi không thực sự có bằng chứng.

Hãy thử: Đây là mã để tạo các dự đoán để bạn có thể chơi và xem nó hiệu quả như thế nào.

#! /usr/bin/python

from fractions import Fraction
import heapq
import readline
import sys

def generate_next_guesses (low, high, limit):
    upcoming = [(low.denominator + high.denominator,
                 low.numerator + high.numerator,
                 low.denominator, low.numerator,
                 high.denominator, high.numerator)]
    guesses = []
    while len(guesses) < limit:
        (mid_d, mid_n, low_d, low_n, high_d, high_n) = upcoming[0]
        guesses.append(Fraction(mid_n, mid_d))
        heapq.heappushpop(upcoming, (low_d + mid_d, low_n + mid_n,
                                     low_d, low_n, mid_d, mid_n))
        heapq.heappush(upcoming, (mid_d + high_d, mid_n + high_n,
                                  mid_d, mid_n, high_d, high_n))
    guesses.sort()
    return guesses

def ask (num):
    while True:
        print "Next guess: {0} ({1})".format(num, float(num))
        if 1 < len(sys.argv):
            wanted = Fraction(sys.argv[1])
            if wanted < num:
                print "too high"
                return 1
            elif num < wanted:
                print "too low"
                return -1
            else:
                print "correct"
                return 0

        answer = raw_input("Is this (h)igh, (l)ow, or (c)orrect? ")
        if answer == "h":
            return 1
        elif answer == "l":
            return -1
        elif answer == "c":
            return 0
        else:
            print "Not understood.  Please say one of (l, c, h)"

guess_size_bound = 2
low = Fraction(0)
high = Fraction(1)
guesses = [Fraction(1,2)]
required_guesses = 0
answer = -1
while 0 != answer:
    if 0 == len(guesses):
        guess_size_bound *= 2
        guesses = generate_next_guesses(low, high, guess_size_bound - 1)
    #print (low, high, guesses)
    guess = guesses[len(guesses)/2]
    answer = ask(guess)
    required_guesses += 1
    if 0 == answer:
        print "Thanks for playing!"
        print "I needed %d guesses" % required_guesses
    elif 1 == answer:
        high = guess
        guesses[len(guesses)/2:] = []
    else:
        low = guess
        guesses[0:len(guesses)/2 + 1] = []

Để làm ví dụ, tôi đã thử 101/1024 (0,0986328125) và thấy rằng phải mất 20 lần đoán để tìm ra câu trả lời. Tôi đã thử 0.98765 và phải mất 45 lần đoán. Tôi đã thử 0,0123456789 và cần 66 lần đoán và khoảng một giây để tạo chúng. (Lưu ý, nếu bạn gọi chương trình với một số hữu tỉ làm đối số, nó sẽ điền vào tất cả các dự đoán cho bạn. Đây là một sự tiện lợi rất hữu ích.)


Tôi không chắc mình hiểu bạn đang nói gì. Bạn có thể làm rõ điều này?
templatetypedef,

@templatetypedef: Điều gì không rõ ràng? Dự đoán đầu tiên của chúng tôi luôn là 1/2. Giả sử rằng câu trả lời trở lại thấp hơn. 3 số tiếp theo trong danh sách phù hợp với điều kiện là 1/3, 1/41/5. Vì vậy, chúng tôi đoán 1/4tiếp theo, sau đó 1/3hoặc1/5 đoán sau. Nếu tiếp tục, chúng ta lấy 7 số trong phạm vi của mình và thiết lập 3 lần đoán tiếp theo. Sau đó, chúng tôi sẽ lấy 15 và thiết lập 4 dự đoán tiếp theo. vv Điều gì là không rõ ràng về điều đó? Tôi đi ngủ đây. Nếu bạn vẫn chưa hiểu vào buổi sáng, tôi sẽ viết một chương trình để phỏng đoán và bạn có thể xem nó hoạt động như thế nào.
btilly

3
@btilly: 7-3, 15-4, (31-5?) ở đâu vậy? Logic đằng sau việc có một hàng đợi các số "đoán tiếp theo" là gì?
stakx - không còn đóng góp vào

2
@Btilly: +1, nhưng có vẻ như bạn chưa thực sự giải quyết được vấn đề chính . Bạn tạo ra các hợp lý Theta (q) và thực hiện tìm kiếm nhị phân trên các số đó. Vì vậy, thời gian chạy là Omega (q), mặc dù bạn thực hiện các truy vấn O (log ^ 2 q). Trên thực tế, Seth có một thuật toán rất giống (và nếu bạn đọc kỹ, anh ta không truy vấn p + q). IMO, vấn đề chính cần giải quyết ở đây là tạo ra các số hữu tỉ O (polylog (q)), thay vì cố gắng giữ số lượng truy vấn O (polylog (q)) bất kể chi phí lưu giữ sách khác.

1
@Seth: Nó là btilly, không phải billy :-) q hoặc p + q, không quan trọng, vì p <q.

4

Tôi đã hiểu! Những gì bạn cần làm là sử dụng phép tìm kiếm song song với phân giác và phân số liên tiếp .

Phép phân tách sẽ cung cấp cho bạn giới hạn đối với một số thực cụ thể, được biểu diễn dưới dạng lũy ​​thừa của hai, và các phân số tiếp tục sẽ lấy số thực và tìm số hữu tỉ gần nhất.

Cách bạn chạy chúng song song như sau.

Ở mỗi bước, bạn có lulà giới hạn dưới và giới hạn trên của đường phân giác. Ý tưởng là, bạn có sự lựa chọn giữa việc giảm một nửa phạm vi phân giác và thêm một số hạng bổ sung làm biểu diễn phân số liên tục. Khi cả hai lucó cùng số hạng tiếp theo là phân số tiếp tục, thì bạn thực hiện bước tiếp theo trong tìm kiếm phân số tiếp tục và thực hiện truy vấn bằng cách sử dụng phân số tiếp tục. Nếu không, bạn giảm một nửa phạm vi bằng cách sử dụng phân giác.

Vì cả hai phương pháp đều làm tăng mẫu số lên ít nhất một hệ số không đổi (phân giác đi theo thừa số là 2, các phân số tiếp tục đi theo ít nhất một thừa số là phi = (1 + sqrt (5)) / 2), điều này có nghĩa là tìm kiếm của bạn phải là O (log (q)). (Có thể lặp lại các phép tính phân số liên tục, vì vậy nó có thể kết thúc là O (log (q) ^ 2).)

Việc tiếp tục tìm kiếm phân số của chúng tôi cần làm tròn đến số nguyên gần nhất, không sử dụng tầng (điều này rõ ràng hơn bên dưới).

Trên đây là loại hình lượn sóng. Hãy sử dụng một ví dụ cụ thể về r = 1/31:

  1. l = 0, u = 1, truy vấn = 1/2. 0 không thể diễn đạt được dưới dạng phân số liên tục, vì vậy chúng tôi sử dụng tìm kiếm nhị phân cho đến khi l! = 0.

  2. l = 0, u = 1/2, truy vấn = 1/4.

  3. l = 0, u = 1/4, truy vấn = 1/8.

  4. l = 0, u = 1/8, truy vấn = 1/16.

  5. l = 0, u = 1/16, truy vấn = 1/32.

  6. l = 1/32, u = 1/16. Bây giờ 1 / l = 32, 1 / u = 16, chúng có các đại diện cfrac khác nhau, vì vậy hãy tiếp tục chia đôi., Query = 3/64.

  7. l = 1/32, u = 3/64, truy vấn = 5/128 = 1 / 25,6

  8. l = 1/32, u = 5/128, truy vấn = 9/256 = 1 / 28,4444 ....

  9. l = 1/32, u = 9/256, truy vấn = 17/512 = 1 / 30.1176 ... (làm tròn đến 1/30)

  10. l = 1/32, u = 17/512, truy vấn = 33/1024 = 1 / 31.0303 ... (làm tròn đến 1/31)

  11. l = 33/1024, u = 17/512, truy vấn = 67/2048 = 1 / 30.5672 ... (làm tròn đến 1/31)

  12. l = 33/1024, u = 67/2048. Tại thời điểm này, cả l và u đều có cùng số hạng tiếp tục là 31, vì vậy bây giờ chúng ta sử dụng phép đoán phân số tiếp tục. truy vấn = 1/31.

SỰ THÀNH CÔNG!

Ví dụ khác, hãy sử dụng 16/113 (= 355/113 - 3 trong đó 355/113 khá gần với số pi).

[được tiếp tục, tôi phải đi đâu đó]


Khi suy ngẫm thêm, các phân số tiếp tục là con đường để đi, đừng bận tâm đến việc phân đôi ngoại trừ việc xác định số hạng tiếp theo. Nhiều hơn khi tôi trở lại.


Bạn chắc chắn vào một cái gì đó ở đây. Tôi nghĩ rằng cách tiếp cận có thể chỉ là sử dụng thuật toán chung chung "Tôi đang nghĩ đến một số nguyên" để tính một số hạng của phân số liên tục tại một thời điểm. Tôi không phải là một chuyên gia về phân số liên tục, nhưng từ những gì tôi thu thập được, chỉ có nhiều số hạng theo lôgarit trong biểu diễn và nếu thủ tục này hoạt động, nó sẽ là một cách để tạo từng số hạng đó một cách sử dụng thời gian lôgarit cho mỗi chúng. Tôi sẽ nghĩ lại điều này.
templatetypedef

Vâng, tôi hoàn toàn đồng ý - CF là câu trả lời đơn giản nhất và có lẽ là hiệu quả nhất, chỉ cần sử dụng tìm kiếm số nguyên cho mỗi thuật ngữ. Tôi đã định đặt đó là câu trả lời của riêng mình nhưng @Jason đã đánh bại tôi.
mokus

1
Không có số hữu tỉ nào được xác định rõ ràng nhất với một số thực nhất định (ngoài chính nó). Cách tiếp cận này đang làm là gì không rõ ràng lắm, có lẽ nó cần được xây dựng thêm.

@Moron: Đọc tiếp các phép tính gần đúng phân số. (ví dụ: Lý thuyết về các con số, Niven & Zuckerman) Chúng tạo thành các số hữu tỉ gần nhất cho một mẫu số ràng buộc, cụ thể là nếu p / q là xấp xỉ phân số liên tục của một số thực r thì | r - (p / q) | <= C / (q ^ 2) trong đó tôi quên C là gì, tôi nghĩ nó là 1/5 hoặc 1 / sqrt (5).
Jason S

Ví dụ: lucó cùng một CF cho đến một thời điểm nào đó không nhất thiết ngụ ý rằng con số bạn đang đoán cũng có cùng một hội tụ ... (nếu tôi hiểu đúng cách tiếp cận của bạn).

3

Tôi nghĩ rằng tôi đã tìm thấy một thuật toán O (log ^ 2 (p + q)).

Để tránh nhầm lẫn trong đoạn tiếp theo, một "truy vấn" đề cập đến thời điểm người đoán đưa cho người thách thức một dự đoán và người thách thức trả lời "lớn hơn" hoặc "nhỏ hơn". Điều này cho phép tôi dành từ "đoán" cho một thứ khác, đoán cho p + q mà không được hỏi trực tiếp cho người thách thức.

Ý tưởng là đầu tiên tìm p + q, sử dụng thuật toán bạn mô tả trong câu hỏi của mình: đoán một giá trị k, nếu k quá nhỏ, hãy nhân đôi nó và thử lại. Sau đó, khi bạn có giới hạn trên và giới hạn dưới, hãy thực hiện tìm kiếm nhị phân tiêu chuẩn. Điều này thực hiện các truy vấn O (log (p + q) T), trong đó T là giới hạn trên cho số lượng truy vấn cần để kiểm tra phỏng đoán. Hãy tìm T.

Chúng ta muốn kiểm tra tất cả các phân số r / s với r + s <= k, và nhân đôi k cho đến khi k đủ lớn. Lưu ý rằng có những phân số O (k ^ 2) bạn cần kiểm tra xem có giá trị cho trước của k hay không. Xây dựng một cây tìm kiếm nhị phân cân bằng chứa tất cả các giá trị này, sau đó tìm kiếm nó để xác định xem p / q có trong cây hay không. Cần các truy vấn O (log k ^ 2) = O (log k) để xác nhận rằng p / q không có trong cây.

Chúng ta sẽ không bao giờ đoán giá trị của k lớn hơn 2 (p + q). Do đó chúng ta có thể lấy T = O (log (p + q)).

Khi chúng tôi đoán đúng giá trị của k (tức là k = p + q), chúng tôi sẽ gửi truy vấn p / q cho người thách đấu trong quá trình kiểm tra đoán của chúng tôi cho k và giành chiến thắng trong trò chơi.

Tổng số truy vấn khi đó là O (log ^ 2 (p + q)).


Trên thực tế, việc xây dựng cây tìm kiếm sẽ mất K ^ 2log K thời gian. Có lẽ bạn nên cải thiện bước này để thực sự mất O (log k) thời gian. Ngoài ra, khi bạn có ứng cử viên k, bạn nên trả về "lớn hơn / nhỏ hơn" cho nó, chứ không chỉ "tồn tại / không tồn tại". Bạn làm nó như thế nào?
Eyal Schneider

Vui lòng bỏ qua phần thứ hai của nhận xét trước của tôi;) Nếu vòng lặp bên ngoài thực hiện nhân đôi, thì phần bên trong chỉ phải kiểm tra khớp / không khớp.
Eyal Schneider

Đây là một thuật toán hay cho các # đối tượng là O (log ^ 2 (p + q)), nhưng không áp dụng cho độ phức tạp thời gian tính toán O (log ^ 2 (p + q)). OP yêu cầu loại phức tạp nào?
Eyal Schneider

Tôi đang tìm kiếm thứ gì đó (lý tưởng là) với cả hai thuộc tính. Đây chắc chắn là một khởi đầu tốt về mặt giảm thiểu số lượng truy vấn, mặc dù lý tưởng nhất là tôi muốn thứ gì đó cũng giảm thiểu việc tính toán liên quan. Sau đó, một lần nữa, điều này có thể là tối ưu về mặt lý thuyết!
templatetypedef,

1
@billy: Thuật toán không đặt trực tiếp câu hỏi p + q. Với k cho trước, nó sẽ kiểm tra (sử dụng tìm kiếm nhị phân) tất cả các phân số r / s với r + s <= k. Nếu p + q <= k, nó tìm thấy câu trả lời; nếu không chúng ta biết p + q> k, vì vậy chúng ta nhân đôi k.
Seth

3

Được rồi, tôi nghĩ tôi đã tìm ra một thuật toán O (lg 2 q) cho vấn đề này dựa trên cái nhìn sâu sắc nhất của Jason S về việc sử dụng phân số liên tục. Tôi nghĩ rằng tôi sẽ hoàn thành thuật toán ngay tại đây để chúng tôi có một giải pháp hoàn chỉnh, cùng với phân tích thời gian chạy.

Trực giác đằng sau thuật toán là bất kỳ số hữu tỉ p / q nào trong phạm vi đều có thể được viết dưới dạng

a 0 + 1 / (a 1 + 1 / (a 2 + 1 / (a 3 + 1 / ...))

Để có sự lựa chọn thích hợp của một i . Đây được gọi là phân số tiếp diễn . Quan trọng hơn, mặc dù những chữ i này có thể được suy ra bằng cách chạy thuật toán Euclide trên tử số và mẫu số. Ví dụ: giả sử chúng ta muốn biểu diễn 14/11 theo cách này. Chúng tôi bắt đầu bằng cách lưu ý rằng 14 tương đương với mười một 0 lần, do đó, ước tính thô của 11/14 sẽ là

0 = 0

Bây giờ, giả sử rằng chúng ta lấy nghịch đảo của phần này để có được 14/11 = 1 3 / 11 . Vì vậy, nếu chúng ta viết

0 + (1/1) = 1

Chúng tôi nhận được giá trị gần đúng hơn một chút là 14/11. Bây giờ chúng ta đang trái với 3/11, chúng ta có thể lấy đối ứng một lần nữa để có được 11/3 = 3 2 / 3 , vì vậy chúng tôi có thể xem xét

0 + (1 / (1 + 1/3)) = 3/4

Đó là một ước lượng tốt khác với 14/11. Bây giờ, chúng ta có 2/3, do đó xem xét các đối ứng, đó là 3/2 = 1 1 / 2 . Nếu sau đó chúng ta viết

0 + (1 / (1 + 1 / (3 + 1/1))) = 5/6

Chúng tôi nhận được một giá trị gần đúng khác là 14/11. Cuối cùng, chúng ta còn lại 1/2, tương ứng là 2/1. Nếu cuối cùng chúng ta viết ra

0 + (1 / (1 + 1 / (3 + 1 / (1 + 1/2)))) = (1 / (1 + 1 / (3 + 1 / (3/2)))) = (1 / (1 + 1 / (3 + 2/3))))) = (1 / (1 + 1 / (11/3)))) = (1 / (1 + 3/11)) = 1 / (14 / 11) = 14/11

đó chính xác là phần chúng tôi muốn. Hơn nữa, hãy nhìn vào chuỗi các hệ số mà chúng ta đã sử dụng. Nếu bạn chạy thuật toán Euclid mở rộng vào ngày 11 và 14, bạn nhận được

11 = 0 x 14 + 11 -> a0 = 0 14 = 1 x 11 + 3 -> a1 = 1 11 = 3 x 3 + 2 -> a2 = 3 3 = 2 x 1 + 1 -> a3 = 2

Nó chỉ ra rằng (sử dụng nhiều toán học hơn tôi hiện biết cách làm!) Rằng đây không phải là sự trùng hợp ngẫu nhiên và các hệ số trong phân số liên tục của p / q luôn được hình thành bằng cách sử dụng thuật toán Euclide mở rộng. Điều này thật tuyệt, vì nó cho chúng ta biết hai điều:

  1. Có thể có nhiều nhất là hệ số O (lg (p + q)), bởi vì thuật toán Euclide luôn kết thúc trong nhiều bước này, và
  2. Mỗi hệ số tối đa là {p, q}.

Với hai sự kiện này, chúng ta có thể đưa ra một thuật toán để khôi phục bất kỳ số hữu tỉ p / q nào, không chỉ những số giữa 0 và 1, bằng cách áp dụng thuật toán chung để đoán từng số nguyên tùy ý n một để khôi phục tất cả các hệ số trong phần tiếp theo cho p / q. Tuy nhiên, hiện tại, chúng ta sẽ chỉ lo lắng về các số trong phạm vi (0, 1], vì logic để xử lý các số hữu tỉ tùy ý có thể được thực hiện dễ dàng với điều này là một chương trình con.

Bước đầu tiên, giả sử rằng chúng ta muốn tìm giá trị tốt nhất của 1 sao cho 1 / a 1 gần nhất có thể với p / q và 1 là một số nguyên. Để làm điều này, chúng tôi chỉ có thể chạy thuật toán để đoán các số nguyên tùy ý, lấy đối ứng mỗi lần. Sau khi làm điều này, một trong hai điều sẽ xảy ra. Đầu tiên, chúng ta có thể tình cờ phát hiện ra rằng p / q = 1 / k đối với một số nguyên k nào đó, trong trường hợp đó chúng ta đã hoàn thành. Nếu không, chúng ta sẽ thấy rằng p / q được kẹp giữa 1 / (a 1 - 1) và 1 / a 0 đối với một số a 1 . Khi chúng ta làm điều này, sau đó chúng ta bắt đầu làm việc với phân số tiếp tục sâu hơn một cấp độ bằng cách tìm a 2 sao cho p / q nằm giữa 1 / (a 2 1 + 1 / a) và 1 / (a 1 + 1 / (a 2 + 1)). Nếu chúng ta tìm thấy p / q một cách kỳ diệu, điều đó thật tuyệt! Nếu không, sau đó chúng ta đi xuống một cấp nữa trong phần tiếp tục. Cuối cùng, chúng tôi sẽ tìm ra con số theo cách này và không thể mất quá nhiều thời gian. Mỗi tìm kiếm nhị phân để tìm một hệ số mất nhiều nhất O (lg (p + q)) thời gian và có nhiều nhất là O (lg (p + q)) mức cho tìm kiếm, vì vậy chúng ta chỉ cần O (lg 2 (p + q)) các phép toán số học và các thăm dò để khôi phục p / q.

Một chi tiết tôi muốn chỉ ra là chúng ta cần theo dõi xem chúng ta đang ở cấp độ lẻ hay cấp độ chẵn khi thực hiện tìm kiếm bởi vì khi chúng ta kẹp p / q giữa hai phân số tiếp tục, chúng ta cần biết liệu hệ số chúng tôi đang tìm kiếm là phần trên hoặc phần dưới. Tôi sẽ nói mà không cần bằng chứng rằng đối với i với i lẻ bạn muốn sử dụng số trên của hai số và với i chẵn, bạn sử dụng số dưới của hai số.

Tôi gần như tự tin 100% rằng thuật toán này hoạt động. Tôi sẽ cố gắng viết ra một bằng chứng chính thức hơn về điều này, trong đó tôi điền vào tất cả các lỗ hổng trong lập luận này, và khi tôi thực hiện, tôi sẽ đăng một liên kết ở đây.

Cảm ơn mọi người đã đóng góp những hiểu biết cần thiết để giải pháp này hoạt động, đặc biệt là Jason S đã đề xuất tìm kiếm nhị phân trên các phân số tiếp tục.


Mới xem cái này, chưa có cơ hội đọc kỹ nhưng có lẽ bạn đã đúng.
Jason S

... mặc dù tôi nghĩ đó là log (q), không phải log ^ 2 (q).
Jason S

Tôi tin rằng điều này là đúng. Để có bằng chứng, hãy xem bình luận của tôi cho câu trả lời đầu tiên của Jason.

Trên thực tế, tôi nghĩ rằng chúng ta có một bằng chứng rằng nó là O (log q). Xem bình luận của tôi cho câu trả lời thứ hai của Jason.

2

Hãy nhớ rằng bất kỳ số hữu tỉ nào trong (0, 1) đều có thể được biểu diễn dưới dạng tổng hữu hạn của các phân số đơn vị riêng biệt (dương hoặc âm). Ví dụ, 2/3 = 1/2 + 1/6 và 2/5 = 1/2 - 1/10. Bạn có thể sử dụng điều này để thực hiện tìm kiếm nhị phân chuyển tiếp.


2
Bạn có thể giải thích thêm về cách thuật toán sẽ sử dụng sự kiện này không?
Seth

Bạn đang nói về phân số Ai Cập?
Gabe

2

Đây là một cách khác để làm điều đó. Nếu có đủ tiền lãi, tôi sẽ cố gắng điền thông tin chi tiết vào tối nay, nhưng tôi không thể ngay bây giờ vì tôi còn trách nhiệm với gia đình. Đây là phần sơ khai của một triển khai sẽ giải thích thuật toán:

low = 0
high = 1
bound = 2
answer = -1
while 0 != answer:
    mid = best_continued_fraction((low + high)/2, bound)
    while mid == low or mid == high:
        bound += bound
        mid = best_continued_fraction((low + high)/2, bound)
    answer = ask(mid)
    if -1 == answer:
        low = mid
    elif 1 == answer:
        high = mid
    else:
        print_success_message(mid)

Và đây là lời giải thích. Điều best_continued_fraction(x, bound)nên làm là tìm gần đúng phân số tiếp tục cuối cùng xvới mẫu số nhiều nhất bound. Thuật toán này sẽ thực hiện các bước polylog để hoàn thành và tìm ra các xấp xỉ rất tốt (mặc dù không phải lúc nào cũng là tốt nhất). Vì vậy, đối với mỗibound chúng tôi sẽ nhận được một cái gì đó gần với tìm kiếm nhị phân thông qua tất cả các phân số có thể có của kích thước đó. Đôi khi, chúng ta sẽ không tìm thấy một phân số cụ thể nào cho đến khi chúng ta tăng giới hạn xa hơn mức chúng ta nên làm, nhưng chúng ta sẽ không xa.

Vì vậy, bạn có nó. Một số logarit các câu hỏi được tìm thấy với bài tập đa thức.

Cập nhật: Và mã làm việc đầy đủ.

#! /usr/bin/python

from fractions import Fraction
import readline
import sys

operations = [0]

def calculate_continued_fraction(terms):
    i = len(terms) - 1
    result = Fraction(terms[i])
    while 0 < i:
        i -= 1
        operations[0] += 1
        result = terms[i] + 1/result
    return result

def best_continued_fraction (x, bound):
    error = x - int(x)
    terms = [int(x)]
    last_estimate = estimate = Fraction(0)
    while 0 != error and estimate.numerator < bound:
        operations[0] += 1
        error = 1/error
        term = int(error)
        terms.append(term)
        error -= term
        last_estimate = estimate
        estimate = calculate_continued_fraction(terms)
    if estimate.numerator < bound:
        return estimate
    else:
        return last_estimate

def ask (num):
    while True:
        print "Next guess: {0} ({1})".format(num, float(num))
        if 1 < len(sys.argv):
            wanted = Fraction(sys.argv[1])
            if wanted < num:
                print "too high"
                return 1
            elif num < wanted:
                print "too low"
                return -1
            else:
                print "correct"
                return 0

        answer = raw_input("Is this (h)igh, (l)ow, or (c)orrect? ")
        if answer == "h":
            return 1
        elif answer == "l":
            return -1
        elif answer == "c":
            return 0
        else:
            print "Not understood.  Please say one of (l, c, h)"

ow = Fraction(0)
high = Fraction(1)
bound = 2
answer = -1
guesses = 0
while 0 != answer:
    mid = best_continued_fraction((low + high)/2, bound)
    guesses += 1
    while mid == low or mid == high:
        bound += bound
        mid = best_continued_fraction((low + high)/2, bound)
    answer = ask(mid)
    if -1 == answer:
        low = mid
    elif 1 == answer:
        high = mid
    else:
        print "Thanks for playing!"
        print "I needed %d guesses and %d operations" % (guesses, operations[0])

Nó có vẻ hiệu quả hơn trong việc phỏng đoán so với giải pháp trước đó và thực hiện ít thao tác hơn. Đối với 101/1024, nó yêu cầu 19 lần đoán và 251 phép toán. Đối với .98765, nó cần 27 lần đoán và 623 phép toán. Đối với 0,0123456789, nó yêu cầu 66 lần đoán và 889 phép toán. Và để cười khúc khích và cười toe toét, đối với 0,0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 (đó là 10 bản sao của bản trước), nó yêu cầu 665 lần đoán và 23289 phép toán.


@ jason-s: Tốt hơn là nên điền ngay bây giờ. Tôi mong được so sánh với mã của bạn khi bạn có mã. Của bạn chắc chắn sẽ yêu cầu ít thao tác hơn, tôi không hiểu ai sẽ yêu cầu ít đoán hơn.
btilly

0

Bạn có thể sắp xếp các số hữu tỉ trong một khoảng thời gian nhất định bằng cặp (mẫu số, tử số). Sau đó, để chơi trò chơi, bạn có thể

  1. Tìm khoảng thời gian [0, N]bằng cách sử dụng phương pháp nhân đôi bước
  2. Cho một lần [a, b]bắn khoảng cho số hữu tỉ với mẫu số nhỏ nhất trong khoảng gần tâm nhất của khoảng đó

điều này tuy nhiên có lẽ vẫn còn O(log(num/den) + den)(không chắc chắn và ở đây còn quá sớm vào buổi sáng để khiến tôi suy nghĩ rõ ràng ;-))

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.