【问题标题】:Binary search for a fraction二分查找分数
【发布时间】:2021-03-07 19:15:18
【问题描述】:

我正在尝试解决 Sedgewick 的 Algorithms 一书中的一个练习,内容如下:

设计一种使用对数形式的查询的方法,该数字是否小于 x?找到一个有理数 p/q 使得 0

我知道我必须进行二分搜索的时间间隔是 ]0, 1[ 但我不确定我应该看什么以及 N 是什么。谁能给我解释一下?

【问题讨论】:

  • 继续阅读Farey sequence
  • N 是给定的常数。提示的要点是,如果您只是询问其分母是 2 的幂次幂的数字,您会得到一个像 1/2、1/4、3/8、...这样的序列,它将在 1/N^2 以内查询的对数。然后你只需要找到最接近那个点的分数。
  • 提示的证明是p/q - p'/q' = (pq' - p'q)/(qq')。如果这些分数不同,则分母的绝对值至少为 1,分子以N^2 为界。

标签: algorithm binary-search


【解决方案1】:

忽略提示,这是一个更难的问题的解决方案。

即通过二分查找找到any有理数,分子/分母的绝对值有对数界限,事先不知道它有多大。

这是 Stern-Brocot 树的二分查找。

class GuessState:
    def __init__ (self):
        self.direction = None
        self.start = [0, 1]
        self.bound = [0, 0]
        self.multiple_upper = 1
        self.multiple_lower = 1
        self.is_widening = True
        self.is_choosing = None

    def next_guess (self):
        if self.is_widening:
            multiple = self.multiple_upper
        else:
            multiple = (self.multiple_lower + self.multiple_upper) // 2
        return (self.start[0] + multiple * self.bound[0], self.start[1] + multiple * self.bound[1])

    def add_response (self, response):
        next_level = False
        if self.direction is None:
            if 0 < response:
                self.bound[0] = 1
                self.direction = 1
            else:
                self.bound[0] = -1
                self.direction = -1
            self.is_choosing = True
            return
        elif self.is_choosing:
            if self.direction * response < 0:
                # Reverse direction.
                self.direction = - self.direction
                (self.start, self.bound) = (self.bound, self.start)
            self.multiple_upper = 2
            self.is_choosing = False
        elif self.is_widening:
            if 0 < response * self.direction:
                self.multiple_lower = self.multiple_upper
                self.multiple_upper += self.multiple_upper
            else:
                self.is_widening = False
                if self.multiple_lower + 1 == self.multiple_upper:
                    next_level = True
        elif self.multiple_lower + 1 < self.multiple_upper:
            if 0 < self.direction * response:
                self.multiple_lower = (self.multiple_lower + self.multiple_upper) // 2
            else:
                self.multiple_upper = (self.multiple_lower + self.multiple_upper) // 2
        else:
            next_level = True

        if next_level:
            next_start = (self.start[0] + self.multiple_lower * self.bound[0], self.start[1] + self.multiple_lower * self.bound[1])
            next_bound = (self.start[0] + self.multiple_upper * self.bound[0], self.start[1] + self.multiple_upper * self.bound[1])
            self.start = next_start
            self.bound = next_bound
            self.multiple_lower = 1
            self.multiple_upper = 1
            self.is_choosing = True
            self.is_widening = True

def guesser (answerer):
    state = GuessState()
    response = answerer(state.next_guess())
    while response != 0:
        state.add_response(response)
        response = answerer(state.next_guess())
    return state.next_guess()

def answerer (answer):
    def compare (guess):
        val = guess[0] / guess[1]
        print(f"Comparing answer {answer} to guess {val} ({guess[0]}/{guess[1]})")
        if val < answer:
            return 1
        elif answer < val:
            return -1
        else:
            return 0
    return compare

print(guesser(answerer(0.124356)))

【讨论】:

    【解决方案2】:

    这里要求你找到最接近给定有理 x 的 n = N-1 阶的Farey number,其中 x 在 [0,1] 内,x 和 N 是输入参数。您可以像下面的 sn-p 一样对最接近 x 的数字进行蛮力搜索:

    public static void BruteForce(double x, int N, out int numerator, out int denominator)
    {
        // Farey sequence of order n = N-1: 
        int n = N - 1;
    
        // Find the closest Farey number [0/1,1/n,...(n-1)/n,1/1] to the 'x' given... 
        double min_diff = 1.0;
        int num_closest = 0;
        int denom_closest = 0;
        for (int denom = 1; denom < n + 1; denom++)
        {
            for (int num = 1; num < denom; num++)
            {
                double r = (double)num / (double)denom;
                double diff = System.Math.Abs(x - r);
                if (diff < min_diff)
                {
                    min_diff = diff;
                    num_closest = num;
                    denom_closest = denom;
                }
            }
        }
    
        numerator = num_closest;
        denominator = denom_closest;
    }
    

    该算法虽然具有二次增长顺序,但不是那么有用。

    更好的方法是利用法雷对的属性。正如the Farey mediant property theorem 所说,如果 p=a/b 和 q=c/d>p 是最低分数的分数,并且它们之间没有分母小于任何一个的分数,则分母最小的分数 ⊕ 是 (a+c)/ (b+d)。这个分数称为“中位数”,它将 Farey 序列分成两个区间 [0/1,...,⊕] 和 [⊕,...,1/1],其中没有分母低于中位数本身的数字.上述定理现在可以应用于包含有理参数 x 的区间。如果找到的中位数的分母高于 Farey 序列的 n 阶,则意味着搜索结束,搜索区间现在只包含两个 Farey 有理数,我们需要选择最接近 x 的一个。 算法如下(感谢Yakov Galka),做了一些细微的调整:

    public static void MediantSearch(double x, int N, out int numerator, out int denominator)
    {
        // Farey sequence of order n = N-1: 
        int order = N - 1;
    
        // Numerator and denominator bounds... 
        int n = order - 1;
        int d = order;
    
        // Define the search bounds... 
        int lo_num = 0; int lo_denom = 1;
        int hi_num = 1; int hi_denom = 1;
    
        while(true)
        {
            int mediant_num = lo_num + hi_num;
            int mediant_denom = lo_denom + hi_denom;
    
            if(mediant_denom > d)
            {
                // Break the search and return the closest... 
                if((x - (double)lo_num/(double)lo_denom) < ((double)hi_num/(double)hi_denom - x))
                {
                    numerator = lo_num;
                    denominator = lo_denom;
                }
                else
                {
                    numerator = hi_num;
                    denominator = hi_denom;
                }
    
                break;
            }
    
            if((double)mediant_num/(double)mediant_denom < x)
            {
                lo_num = mediant_num;
                lo_denom = mediant_denom;
            }
            else if((double)mediant_num / (double)mediant_denom > x)
            {
                hi_num = mediant_num;
                hi_denom = mediant_denom;
            }
            else
            {
                numerator = mediant_num;
                denominator = mediant_denom;
                break;
            }
        }
    }
    

    该算法具有对数增长顺序 O(log(n))。

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 2015-12-10
      • 1970-01-01
      • 1970-01-01
      • 2014-11-13
      • 2017-05-16
      • 2012-12-24
      • 2015-07-25
      相关资源
      最近更新 更多