Нахождение ближайшей целочисленной дроби к заданному случайному вещественному числу между 0..1, заданным диапазонам числителя и знаменателя

Учитывая два диапазона положительных целых чисел x: [1... n] и y: [1... m] и случайного вещественного числа R от 0 до 1, мне нужно найти пару элементов (i, j) из x и у такой, что x_i/y_j ближе всего к R.

Какой самый эффективный способ найти эту пару?

Ответ 1

Используйте последовательность Фарея.

  1. Начните с a = 0, b = 1 и A = {ближайший из a и b к R}.
  2. Пусть c будет следующей дробью Фари между a и b, заданной как c = (num (a) + num (b))/(denom (a) + denom (b)) (обязательно делите num (c) и denom (c) gcd (num (c), denom (c))): enter image description here
  3. Если числитель или знаменатель c выходит за пределы диапазона ввода, выведите A и остановите.
  4. Если c ближе к R, чем A, установите A на c.
  5. Если R находится в [a, c], установите b = c, в противном случае установите a = c.
  6. Перейти к 2.

Это находит наилучшее приближение в пространстве O (1), наихудшем времени O (M) и в среднем O (log M).

Ответ 2

Стандартным подходом к аппроксимации реалов рациональными является вычисление серии цепных дробей (см. [1]). Положите предел на номинатор и знаменатель при вычислении частей серии, а последнее значение до того, как вы нарушите ограничения, - это фракция, очень близкая к вашему действительному числу.

Это очень хорошее приближение очень быстро, но я не уверен, что это всегда найдет самое близкое приближение. Известно, что

любое сходящееся [частичное значение продолжения дробных фракций] ближе к продолженной дроби, чем любая другая фракция, знаменатель которой меньше, чем у сходящегося

но могут быть приближения с большим знаменателем (все еще ниже вашего предела), которые являются лучшими приближениями, но не являются сходящимися.

[1] http://en.wikipedia.org/wiki/Continued_fraction

Ответ 3

Учитывая, что R - вещественное число, такое, что 0 <= R <= 1, целые числа x: [1 ... n] и целые числа y: [1 ... m]. Предполагается, что n <= m, так как если n > m, то x[n]/y[m] будет больше, чем 1, которое не может быть ближайшим приближением к R.

Следовательно, наилучшее приближение R с знаменателем d будет либо floor(R*d) / d, либо ceil(R*d) / d.

Проблема может быть решена в O(m) времени и O(1) space (в Python):

from __future__ import division
from random import random
from math import floor

def fractionize(R, n, d):
    error = abs(n/d - R)
    return (n, d, error)  # (numerator, denominator, absolute difference to R)

def better(a, b):
    return a if a[2] < b[2] else b

def approximate(R, n, m):
    best = (0, 1, R)
    for d in xrange(1, m+1):
        n1 = min(n, int(floor(R * d)))
        n2 = min(n, n1 + 1) # ceil(R*d)
        best = better(best, fractionize(R, n1, d))
        best = better(best, fractionize(R, n2, d))
    return best

if __name__ == '__main__': 
    def main():
        R = random()
        n = 30
        m = 100
        print R, approximate(R, n, m)
    main()

Ответ 4

Prolly get flamed, но поиск может быть лучшим, когда мы вычисляем все дробные значения для каждого из возможных значений. Таким образом, просто индексирование массива 2d, индексированного через дробные части с элементом массива, содержащим реальный эквивалент. Я предполагаю, что у нас есть отдельные части X и Y, поэтому это конечно, это было бы не так. Ahh да, фактическая часть поиска.... erm reet....

Ответ 5

Решение: Вы можете сделать это O (1) и O (m log (n)):

нет необходимости создавать список для поиска,

Псевдокод может быть ошибочным, но идея такова:

r: input number to search.
n,m: the ranges.

for (int i=1;i<=m;i++)
{
    minVal = min(Search(i,1,n,r), minVal);
}

//x and y are start and end of array:
decimal Search(i,x,y,r)
{
   if (i/x > r)
      return i/x - r;

   decimal middle1 = i/Cill((x+y)/2); 
   decimal middle2 = i/Roof((x+y)/2);

   decimal dist = min(middle1,middle2)

   decimal searchResult = 100000;

   if( middle > r)
     searchResult = Search (i, x, cill((x+y)/2),r)
  else
     searchResult = Search(i, roof((x+y)/2), y,r)

  if  (searchResult < dist)
     dist = searchResult;

  return dist;
}

найти индекс как домашнюю работу для читателя.

Описание: Я думаю, вы можете понять, что идея по коду, но проследить один из циклов for: когда я = 1:

вы должны искать в следующих номерах: 1,1/2,1/3,1/4,...., 1/п вы проверяете число с помощью (1,1/cill (n/2)) и (1/floor (n/2), 1/n) и выполняете аналогичный двоичный поиск на нем, чтобы найти наименьший.

Должно сделать это для цикла для всех элементов, поэтому это будет сделано м. и в каждый раз он принимает O (log (n)). эта функция может улучшить некоторые математические правила, но это будет сложно, я пропущу ее.

Ответ 6

Вместо поиска полностью грубой силы выполните линейный поиск по кратчайшему из ваших списков, используя раунд, чтобы найти наилучшее соответствие для каждого элемента. Может быть, что-то вроде этого:

best_x,best_y=(1,1)
for x in 1...n:
    y=max(1,min(m,round(x/R)))
    #optional optimization (if you have a fast gcd)
    if gcd(x,y)>1:
        continue

    if abs(R-x/y)<abs(R-bestx/besty):
        best_x,best_y=(x,y)
return (best_x,best_y)

Не уверен, будет ли оптимизация gcd "быстрее"...