77

私はかつて面接の質問として以下を受け取りました:

私は正の整数nを考えています。O(lg n)クエリでそれを推測できるアルゴリズムを考え出します。それぞれの質問はあなたが選んだ数であり、私は「低い」、「高い」、または「正しい」のいずれかに答えます。

この問題は、修正された二分探索によって解決できます。この探索では、nを超えるものが見つかるまで2の累乗をリストし、その範囲で標準の二分探索を実行します。これについて私がとてもクールだと思うのは、ブルートフォースだけでなく、無限のスペースで特定の数をすばやく検索できることです。

しかし、私が持っている質問は、この問題のわずかな修正です。正の整数を選択する代わりに、0から1までの任意の有理数を選択するとします。私の質問は、私が選んだ有理数を最も効率的に決定するためにどのアルゴリズムを使用できるかということです。

現在、私が持っている最善の解決策は、すべての有理数の二分探索木であるスターン・ブロコット木を暗黙的に歩くことによって、最大でO(q)時間でp/qを見つけることができます。ただし、整数の場合に取得したランタイムに近いランタイムを取得したいと考えていました。おそらく、O(lg(p + q))やO(lg pq)のようなものです。この種のランタイムを取得する方法を知っている人はいますか?

私は当初、区間[0、1]の標準的な二分探索を使用することを検討しましたが、これでは、ほとんどすべての有理数が欠落している、繰り返されない2分表現の有理数しか見つかりません。有理数を列挙する他の方法を使用することも考えましたが、比較が大きい/等しい/小さいだけでは、この空間を検索する方法が見つからないようです。

4

8 に答える 8

50

さて、これが連分数だけを使った私の答えです。

まず、ここでいくつかの用語を取得しましょう。

X = p/qを未知の分数とします。

Q(X、p / q)= sign(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 <p / q <1の場合は、次のアルゴリズムに従います。

  1. Y = 0 = [0]、Z = 1 = [1]、k=0を初期化します。

  2. 外側のループ前提条件は次のとおりです。

    • YとZは、k + 1項の連分数であり、最後の要素が1だけ異なることを除いて同一であるため、Y = [y 0 ; y 1、y 2、y 3、... yk ]およびZ=[y 0 ; y 1、y 2、y 3、... y k + 1]

    • (-1)k(YX)<0 <(-1)k(ZX)、または簡単に言えば、k偶数の場合はY <X <Z、k奇数の場合はZ<X<Y。

  3. 数値の値を変更せずに、連分数の次数を1ステップ拡張します。一般に、最後の項がykおよびyk + 1の場合、それを[... y k、y k +1 =∞]および[... yk、z k + 1 =1]に変更します。ここで、kを1増やします。

  4. 内部ループ:これは、整数に関する@templatetypedefのインタビューの質問と本質的に同じです。近づけるために、2段階の二分探索を行います。

  5. 内部ループ1:y k =∞、z k = a、XはYとZの間にあります。

  6. Zの最後の項を2倍にします。M=Zを計算しますが、m k = 2 * a = 2 *zk使用します。

  7. 未知数を照会します:q = Q(X、M)。

  8. q = 0の場合、答えが得られ、ステップ17に進みます。

  9. qとQ(X、Y)の符号が反対の場合は、XがYとMの間にあることを意味するため、Z = Mに設定して、手順5に進みます。

  10. それ以外の場合は、Y = Mに設定し、次の手順に進みます。

  11. 内部ループ2。yk =b、z k = a、XはYとZの間にあります。

  12. aとbの差が1の場合は、YとZを入れ替えて、手順2に進みます。

  13. 二分探索を実行します。Mを計算します。ここで、m k = floor((a + b)/ 2、およびクエリq = Q(X、M)です。

  14. q = 0の場合、これで完了です。手順17に進みます。

  15. qとQ(X、Y)の符号が反対の場合は、XがYとMの間にあることを意味するため、Z = Mに設定して、手順11に進みます。

  16. それ以外の場合、qとQ(X、Z)の符号は逆になります。つまり、XはZとMの間にあるため、Y = Mに設定して、手順11に進みます。

  17. 完了:X=M。

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!

Mを計算する各ステップで、間隔の範囲が狭くなります。間隔が各ステップで少なくとも1/sqrt(5)の係数で減少することを証明するのはおそらくかなり簡単です(これは行いませんが)。これは、このアルゴリズムがO(log q)ステップであることを示します。

これはtemplatetypedefの元のインタビューの質問と組み合わせて、最初にQ(X、0)を計算し、次に正/負の整数のいずれかに対して、2つの連続する間に境界を置くことにより、0と1の間だけでなく、任意の有理数p/qに適用できることに注意してください。整数、そして小数部に上記のアルゴリズムを使用します。

次に機会があれば、このアルゴリズムを実装するPythonプログラムを投稿します。

編集:また、各ステップで連分数を計算する必要がないことに注意してください(O(k)になります。O(1)の前のステップから次のステップを計算できる連分数の部分的な近似があります。 )。

編集2:部分近似の再帰的定義:

A k =[ a0 ;の場合 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 / q2 。

これは、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)。これから|YZ|を示すことができるはずです。このアルゴリズムによって生成される小さな間隔ごとに、少なくとも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数学を処理し、このプログラムは整数数学(区間計算を除く)のみを使用するため、任意の有理数で機能するはずです。


編集3:これがO(log ^ 2 q)ではなくO(log 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と帰納法を使用して、目的の結果を証明できます。

このように述べましょう。k番目の項に到達するために必要なNk = sum(n k )ステップ後のqの値は、最小値を持ちます。いくつかの固定定数A、cに対してq> = A * 2cN 。(逆にすると、ステップ数Nは<=(1 / c)* log 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は各項を2倍にすることはできません(反例:[0; 1、1、1、1、1]の成長因子はphi =(1 + sqrt(5))/ 2)ので、c =1/を使用しましょう。 4.4。

誘導:

  • 項kの場合、q k = a k q k-1 +qk -2。この場合も、この項に必要なn k = 2b-1ステップの場合、a k > = 2 b-1 = 2 (n k -1)/2です。

    したがって、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

ああ-ここで難しいのは、a k = 1の場合、qはその1つの項であまり増加しない可能性があり、q k-2を使用する必要がありますが、それはqk -1よりもはるかに小さい可能性があります。

于 2011-03-26T22:56:16.933 に答える
6

有理数を誘導型で取り、最初に分母、次に分子の順に書き出してみましょう。

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

私たちの最初の推測はになります1/2。次に、範囲内に3つになるまで、リストに沿って進みます。次に、2つの推測でそのリストを検索します。次に、残りの範囲が7になるまで、リストに沿って進みます。次に、3つの推測でそのリストを検索します。等々。

nステップでは、最初の可能性について説明します。これは、探していた効率の大きさのオーダーです。2O(n)

更新:人々はこの背後にある理由を理解していませんでした。推論は簡単です。二分木を効率的に歩く方法を知っています。分母が最大の分数があります。したがって、特定の分母のサイズまで段階的に検索できます。問題は、検索する可能性のある有理数が無限にあることです。したがって、それらをすべて並べて注文し、検索を開始することはできません。O(n2)nO(2*log(n)) = O(log(n))

したがって、私の考えは、いくつかを並べて、検索し、さらに並べて、検索するなどです。さらに並ぶたびに、前回の約2倍になります。したがって、前回よりも1つ多く推測する必要があります。したがって、最初のパスでは1つの推測を使用して、1つの可能な有理数をトラバースします。2つ目は、2つの推測を使用して、3つの可能な有理数をトラバースします。3つ目は、3つの推測を使用して、7つの可能な有理数をトラバースします。そして、私たちkの'thは、推測を使用して、考えられる有理数kをトラバースします。特定の有理数については、最終的には、その有理数をかなり大きなリストに入れて、効率的に二分探索を行う方法を知っていることになります。2k-1m/n

二分探索を行い、さらに有理数を取得するときに学んだことをすべて無視した場合、すべての有理数をm/nパスO(log(n))に含めます。(それは、その時点までに、それまでのすべての有理数を含むのに十分な有理数を持つパスに到達するためm/nです。)しかし、各パスはより多くの推測を必要とするため、それは推測になります。O(log(n)2)

しかし、実際にはそれよりもはるかに優れています。最初の推測では、リストにある有理数の半分が大きすぎるか小さすぎるかを排除します。次の2つの推測では、スペースを4分の1に完全に分割することはできませんが、スペースからそれほど遠くはありません。次の3つの推測でも、スペースを8分の1に完全に削減することはできませんが、スペースからそれほど遠くはありません。等々。あなたがそれをまとめるとき、私はあなたがm/n段階的に見つけるという結果であると確信していO(log(n))ます。私は実際に証拠を持っていませんが。

試してみてください:これは、推測を生成して、それがどれほど効率的であるかを確認できるようにするためのコードです。

#! /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] = []

試してみる例として、101/1024(0.0986328125)を試してみたところ、答えを見つけるのに20回の推測が必要であることがわかりました。0.98765を試しましたが、45回の推測が必要でした。0.0123456789を試しましたが、66の推測と、それらを生成するのに約1秒かかりました。(引数として有理数を使用してプログラムを呼び出すと、すべての推測が入力されることに注意してください。これは非常に便利です。)

于 2011-03-26T07:40:09.733 に答える
4

私はそれを持っている!あなたがする必要があるのは、二分法と連分数で並列検索を使用することです。

二等分線は、2の累乗として表される特定の実数に制限を与え、連分数は実数を取り、最も近い有理数を見つけます。

それらを並行して実行する方法は次のとおりです。

各ステップで、二等分線の下限と上限がありますluアイデアは、二分法の範囲を半分にするか、連分数表現として追加の項を追加するかを選択できるということです。両方が連分数と同じ次の項lu持っている場合、連分数検索の次のステップに進み、連分数を使用してクエリを実行します。それ以外の場合は、二等分線を使用して範囲を半分にします。

どちらの方法でも分母が少なくとも一定の係数で増加するため(二等分線は2倍になり、連分数は少なくともphi =(1 + sqrt(5))/ 2の係数になります)、これは検索がOである必要があることを意味します。 (log(q))。(連分数の計算が繰り返される可能性があるため、O(log(q)^ 2)になる可能性があります。)

連分数検索では、floorを使用せずに、最も近い整数に丸める必要があります(これは以下でより明確になります)。

上記はちょっと手ぶれです。r=1/31の具体的な例を使用してみましょう。

  1. l = 0、u = 1、クエリ=1/2。0は連分数として表現できないため、l!=0になるまで二分探索を使用します。

  2. l = 0、u = 1/2、クエリ=1/4。

  3. l = 0、u = 1/4、クエリ=1/8。

  4. l = 0、u = 1/8、クエリ=1/16。

  5. l = 0、u = 1/16、クエリ=1/32。

  6. l = 1/32、u=1/16。現在、1 / l = 32、1 / u = 16であり、これらは異なるcfrac担当者を持っているため、二等分し続けます。、query=3/64。

  7. l = 1/32、u = 3/64、クエリ= 5/128 = 1 / 25.6

  8. l = 1/32、u = 5/128、クエリ= 9/256 = 1 /28.4444...。

  9. l = 1/32、u = 9/256、クエリ= 17/512 = 1 / 30.1176 ...(1/30に丸める)

  10. l = 1/32、u = 17/512、クエリ= 33/1024 = 1 / 31.0303 ...(1/31に丸める)

  11. l = 33/1024、u = 17/512、クエリ= 67/2048 = 1 / 30.5672 ...(1/31に丸める)

  12. l = 33/1024、u=67/2048。この時点で、lとuの両方が同じ連分数項31を持っているので、ここで連分数推定を使用します。クエリ=1/31。

成功!

別の例として、16/113(= 355/113-3、ここで355/113は円周率にかなり近い)を使用してみましょう。

[続けるには、どこかに行かなければならない]


さらに考えてみると、連分数が進むべき道であり、次の用語を決定することを除いて、二等分を気にしないでください。私が戻ったときにもっと。

于 2011-03-26T14:11:46.350 に答える
3

O(log ^ 2(p + q))アルゴリズムを見つけたと思います。

次の段落での混乱を避けるために、「クエリ」とは、推測者がチャレンジャーに推測を与え、チャレンジャーが「大きい」または「小さい」と応答する場合を指します。これにより、「推測」という単語を他の何かのために予約することができます。これは、チャレンジャーに直接尋ねられないp+qの推測です。

アイデアは、質問で説明したアルゴリズムを使用して、最初にp + qを見つけることです。値kを推測し、kが小さすぎる場合は、それを2倍にして再試行します。次に、上限と下限が決まったら、標準の二分探索を実行します。これにはO(log(p + q)T)クエリが必要です。ここで、Tは、推測をチェックするために必要なクエリ数の上限です。Tを見つけましょう。

r + s<=kのすべての分数r/sをチェックし、kが十分に大きくなるまでkを2倍にします。kの特定の値をチェックする必要があるO(k ^ 2)分数があることに注意してください。これらすべての値を含む平衡二分探索木を構築し、それを検索してp/qがツリーにあるかどうかを判断します。p / qがツリーにないことを確認するには、O(log k ^ 2)= O(log k)クエリが必要です。

2(p + q)より大きいkの値を推測することは決してありません。したがって、T = O(log(p + q))を取ることができます。

kの正しい値を推測すると(つまり、k = p + q)、kの推測を確認する過程で、クエリp / qをチャレンジャーに送信し、ゲームに勝ちます。

その場合、クエリの総数はO(log ^ 2(p + q))になります。

于 2011-03-26T08:15:44.133 に答える
3

さて、連分数の使用に関するJason Sの最も優れた洞察に基づいて、この問題のO(lg 2 q)アルゴリズムを理解したと思います。実行時の分析とともに完全なソリューションが得られるように、ここでアルゴリズムを完全に具体化すると思いました。

アルゴリズムの背後にある直感は、範囲内の任意の有理数p/qは次のように書くことができるということです。

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

aiの適切な選択について。これは連分数と呼ばれます。さらに重要なことに、これらのa iは、分子と分母でユークリッドアルゴリズムを実行することで導出できます。たとえば、11/14をこのように表現したいとします。まず、14が11のゼロ回になることに注意することから始めます。したがって、11/14の大まかな近似は次のようになります。

0 = 0

ここで、この分数の逆数をとって14/11 =13/11を取得するます。だから私たちが書くなら

0 +(1/1)= 1

11/14に少し良く近似します。3/11が残ったので、逆数を取り直して11/3 = 3 2/3を取得できるので、次のように考えることができます

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

これは、11/14のもう1つの適切な近似値です。ここで、2/3があるので、逆数、つまり3/2 =11 / 2考えます。それから書くなら

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

11/14への別の良い近似が得られます。最後に、逆数が2/1である1/2が残ります。やっと書き出せば

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)= 11/14

これはまさに私たちが望んでいた分数です。さらに、最終的に使用した係数のシーケンスを見てください。11と14で拡張ユークリッドアルゴリズムを実行すると、次のようになります。

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

(私が現在知っているよりも多くの数学を使用して!)これは偶然の一致ではなく、p/qの連分数の係数は常に拡張ユークリッドアルゴリズムを使用して形成されることがわかります。これは素晴らしいことです。2つのことを教えてくれるからです。

  1. ユークリッドの互除法は常にこの多くのステップで終了するため、最大でO(lg(p + q))の係数が存在する可能性があります。
  2. 各係数は最大でmax{p、q}です。

これらの2つの事実を踏まえると、任意の整数nを一度に1つずつ推測する一般的なアルゴリズムを適用して、 p/qの連分数。ただし、ここでは、(0、1]の範囲の数値についてのみ心配します。これは、任意の有理数を処理するためのロジックを、これをサブルーチンとして簡単に実行できるためです。

最初のステップとして、 1 /a1がp/qにできるだけ近く、1が整数になるように、 1の最適な値を見つけたいとしましょう。これを行うには、アルゴリズムを実行して任意の整数を推測し、毎回逆数を取ります。これを行った後、2つのことのうちの1つが起こります。まず、偶然の一致によって、ある整数kに対してp / q = 1 / kであることがわかります。この場合、これで完了です。そうでない場合は、p /qが1 /(a 1 --1)と1 / a0の間に挟まれていることがわかります。これを行うと、p /qが1/(a 1 + 1 / a )の間にあるようなa 2を見つけることにより、1レベル深い連分数の作業を開始します。2)および1 /(a 1 + 1 /(a 2 + 1))。魔法のようにp/qを見つけたら、それは素晴らしいことです。それ以外の場合は、連分数でさらに1レベル下に移動します。最終的には、この方法で番号を見つけることができ、それほど長くはかからないでしょう。係数を見つけるための各二分探索には最大でO(lg(p + q))時間がかかり、検索には最大でO(lg(p + q))レベルがあるため、必要なのはO(lg 2(p + q))p/qを回復するための算術演算とプローブ。

私が指摘したい詳細の1つは、検索を行うときに、奇数レベルか偶数レベルかを追跡する必要があるということです。これは、2つの連分数の間にp / qを挟むときに、係数が私たちが探していたのは、上位または下位の分数でした。iが奇数の場合は、2つの数値の大きい方を使用し、iの場合は、2つの数値の小さい方を使用することを証明せずに述べます。

私はこのアルゴリズムが機能することをほぼ100%確信しています。この推論のすべてのギャップを埋める、より正式な証明を作成しようと思います。その場合は、ここにリンクを投稿します。

このソリューションを機能させるために必要な洞察を提供してくれたすべての人に感謝します。特に、連分数の二分探索を提案してくれたJasonSに感謝します。

于 2011-03-26T22:51:06.460 に答える
2

(0、1)の有理数は、別個の(正または負の)単位分数の有限和として表すことができることに注意してください。たとえば、2/3 = 1/2+1/6および2/5=1/2-1/10です。これを使用して、簡単な二分探索を実行できます。

于 2011-03-26T12:45:58.323 に答える
2

これを行うためのさらに別の方法があります。十分な関心があれば今夜詳細を記入しようと思いますが、家族の責任があるので今はできません。アルゴリズムを説明する必要がある実装のスタブは次のとおりです。

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)

そしてここに説明があります。何をすべきかは、最大で分母を使用しbest_continued_fraction(x, bound)た最後の連分数近似を見つけることです。このアルゴリズムは、ポリログステップを実行して完了し、非常に優れた(常に最良とは限りませんが)近似を見つけます。したがって、それぞれについて、そのサイズのすべての可能な部分を介してバイナリ検索に近いものを取得します。場合によっては、境界を必要以上に増やすまで特定の分数が見つからないことがありますが、それほど遠くはありません。xboundbound

だからあなたはそれを持っています。ポリログ作業で見つかった質問の対数数。

更新:そして完全に機能するコード。

#! /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])

以前のソリューションよりも推測がわずかに効率的であり、実行する操作がはるかに少なくなっています。101/1024の場合、19回の推測と251回の操作が必要でした。.98765の場合、27回の推測と623回の操作が必要でした。0.0123456789の場合、66回の推測と889回の操作が必要でした。そして、笑い声とにやにや笑いの場合、0.0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(前のものの10コピー)の場合、665回の推測と23289回の操作が必要でした。

于 2011-03-26T21:02:38.087 に答える
0

たとえば、ペア(分母、分子)によって、指定された間隔で有理数を並べ替えることができます。次に、ゲームをプレイすることができます

  1. [0, N]倍増ステップアプローチを使用して間隔を見つける
  2. [a, b]区間の中心に最も近い区間で分母が最小の有理数の区間シュートが与えられます

しかし、これはおそらくまだですO(log(num/den) + den)(確かではなく、ここでは早すぎてはっきりと考えさせることができません;-))

于 2011-03-26T07:37:53.930 に答える