【问题标题】:Binary Search to Compute Square root (Java)二分搜索计算平方根 (Java)
【发布时间】:2011-04-15 12:38:30
【问题描述】:

我需要帮助编写一个程序,该程序使用二分搜索递归计算输入非负整数的平方根(向下舍入到最接近的整数)。

这是我目前所拥有的:

import java.util.Scanner;

public class Sqrt {

  public static void main(String[] args) {

    Scanner console = new Scanner(System.in);

    System.out.print("Enter A Valid Integer: ");

    int value = console.nextInt();

    calculateSquareRoot(value);

  }

    public static int calculateSquareRoot(int value) {
      while (value > 0) {
      double sqrt = (int) Math.sqrt(value);
      System.out.println(sqrt);
    }
    return -1;
    }
}

它必须使用二进制搜索来计算平方根这一事实让我感到困惑。如果有人对如何做到这一点有任何建议,将不胜感激。谢谢

【问题讨论】:

  • 如果这是家庭作业,应该这样标记。 (我没有代表这样做。)

标签: java recursion binary-search square-root


【解决方案1】:

本质上,这个想法是您可以使用二分搜索来更接近答案。

例如,假设您有 14 作为输入。然后,您确定 14 的平方根介于 0 和 14 之间。因此,0 和 14 是您当前的“边界”。您将这两个端点一分为二并获得中点: 7. 然后您尝试将 7 作为候选 - 如果 7 的平方大于 14,则您有一个新边界 (0,7);否则你会有一个新的边界 (7,14)。

你不断重复这个二等分,直到你“足够接近”答案,例如你有一个在 14-0.01 和 14+0.01 之间的数平方 - 然后你声明它作为答案。

好的,这么多的提示对硬件来说应该足够好了。不要忘记引用 StackOverflow。

【讨论】:

  • 请注意,根据我的回答,将端点一分为二等同于使用 C == 1 / 2。因此,如果您希望输入在 (1 / C) ^ 2 == 4 左右,则将端点一分为二才是最好的 C。
【解决方案2】:

这是Java中使用二分搜索的递归解决方案:

public class FindSquareRoot {

    public static void main(String[] args) {
        int inputNumber = 50;
        System.out.println(findSquareRoot(1, inputNumber, inputNumber));
    }

    public static int findSquareRoot(int left, int right, int inputNumber){

        // base condition
        if (inputNumber ==0 || inputNumber == 1){
            return inputNumber;
        }

        int mid = (left + right)/2;

        // if square of mid value is less or equal to input value and 
        // square of mid+1 is less than input value. We found the answer. 
        if (mid*mid <= inputNumber && (mid+1)*(mid+1) > inputNumber){
            return mid;
        }

        // if input number is greater than square of mid, we need 
        // to find in right hand side of mid else in left hand side.
        if (mid*mid < inputNumber){
            return findSquareRoot(mid+1, right, inputNumber);
        }
        else{
            return findSquareRoot(left, mid-1, inputNumber);
        }

    }
}

【讨论】:

    【解决方案3】:

    你可以使用这个java方法(迭代)

    public class Solution {
        // basic idea is using binary search
        public int sqrt(int x) {
            if(x == 0 || x == 1) {
                return x;
            }
            int start = 1, end = x / 2;
            while(start <= end) {
                int mid = start + (end - start) / 2;
                if(mid == x / mid) {
                    return mid;
                }
                if(mid < x / mid) {
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }
    
            return start - 1;
        }
    }
    

    您可以驱动自己的递归方法

    【讨论】:

      【解决方案4】:

      edst解决方案不错,但是第11行有错误:

      mid = (high - low) / 2;
      

      应该是

      mid = low + (high - low) / 2;
      

      【讨论】:

        【解决方案5】:

        迭代二元解:

        public static double sqrt(int n) {
        
            double low = 0;
            double high = n;
            double mid = (high - low) / 2;
        
            while (Math.abs((mid * mid) - n) > 0.000000000001) {
                if ((mid * mid) > n) {
        
                    high = mid;
                    mid = (high - low) / 2;
        
                } else{
        
                    low = mid;
                    mid = mid + ((high - low) / 2);
        
                }
            }
            return mid;
        }
        

        【讨论】:

          【解决方案6】:

          我在您的问题中看到了两个重要的计算概念。第一个是二分查找,第二个是递归。由于这是家庭作业,因此这是对理解二分搜索、递归以及如何思考它们的贡献。

          将二分搜索视为将解决方案“空间”分成两半,保留解决方案所在的一半并连续执行此操作,以便过程收敛于解决方案。这样做的关键概念是您需要设计一个具有以下属性的解决方案“空间”:

          1) 可以细分,通常是两半或至少两片

          2) 细分后的两部分,有一种方法可以确定哪一半有解,这样这个过程就可以只在一半上重复。

          递归涉及调用自身的函数(O-O 中的方法)。递归对于收敛到结论的过程非常有效。它要么永远递归,要么直到你用完一些资源,通常是内存,然后它会致命地停止。递归的两个关键概念是:

          1) 通过一些不变性收敛(下文将详细介绍不变性)。

          2) 终止条件(识别充分收敛的条件)。

          现在,为您的平方根例程。该例程的要求是:

          1) 整数输入。

          2) 整数平方根近似,给出最接近实际平方根的下限整数。

          3) 使用递归。

          4) 使用二分查找。

          这有助于了解一些关于平方根的数学知识。初等微积分和解析几何概念也很有帮助。让我们做一些推理。

          我们有一个任意的正整数 x。我们想要它的根 y。如果我们为 y 选择一些测试值,如果 y * y = x,我们可以查看它是否是 x 的根。如果 y 太大,则 y * y > x。如果 y 太小,则 y * y

          这里有一些数学推理可以提供帮助。我们知道 x = y * y 其中 y 是 x 的平方根。这意味着:y = x/y。

          嗯...如果 y 大到成为 x 的平方根会怎样?然后: x

          这会收敛吗?好吧,在使用正实数的数学中,平均值总是高于该值,但每次迭代都越来越接近。这满足了我们依次将解“空间”分成两部分并知道要保留哪两部分的条件。在这种情况下,我们连续计算低于先前值和答案仍然存在的新值,允许我们丢弃高于新值的所有值。当我们达到不再存在高于答案的新值的条件时,我们停止。然而,使用计算机会产生实数的二进制近似值。对于整数,除法有截断。这可能对收敛产生有利或不利的影响。此外,您的答案应该是小于或等于平方根的最大整数。看看我们将获得的那种融合是明智的。

          由于整数除法转换,y1 = (x/y0 + y0)/2 将收敛,直到连续迭代达到整数根或(即小于该根的最大整数)的下限值。这是理想的。如果我们从一个必须大于根的建议值开始,比如 x 本身,那么 yn * yn

          简单的答案是,当我们从 y0 > y 开始时,第一个小于或等于 y 的新 yn,然后 y - yn

          以下是基本的迭代和递归解决方案。解决方案不包含安全功能以确保不为 x 输入负值。一个主要的问题是避免除以零,以防有人想要找到 0 的平方根。由于这是一个简单的答案,递归和迭代方法都在除以零之前返回 0。递归和迭代解决方案都适用于寻找 0 和 1 的平方根的平凡案例。

          还有另一种分析,在 Java 中总是必须使用 int 和 long 算术来完成。一个主要问题是整数溢出,因为 Java 对 int 或 long 溢出没有任何作用。溢出会导致二进制补码值(在别处查找),这可能导致虚假结果,Java 不会抛出 int 或 long 溢出异常。

          在这种情况下,很容易避免可能导致 x 值较大的内部溢出的算术。如果我们创建一个终止条件,例如 y0 * y0 y。 x / 2 适用于 x > 1 的所有值。由于 x 为 0 或 1 的 x 的平方根就是 x,我们可以轻松地测试这些值并简单地返回正确且微不足道的值。您可以构造代码以防止使用值 Integer.MAX_VALUE。如果我们使用 long 而不是 int,同样可以应用。欢迎来到现实世界中的计算!

          public static int intSqRootRecursive (int x) {
              // square roots of 0 and 1 are trivial and x / 2 for
              // the y0 parameter will cause a divide-by-zero exception
              if (x == 0 || x == 1) {
                  return x;
              }
              // starting with x / 2 avoids overflow issues
              return intSqRootRecursive (x, x / 2);
          } // end intSqRootRecursive
          
          private static int intSqRootRecursive(int x, int y0) {
              // square roots of 0 and 1 are trivial
              // y0 == 0 will cause a divide-by-zero exception
              if (x == 0 || x == 1) {
                  return x;
              } // end if
              if (y0 > x / y0) {
                  int y1 = ((x / y0) + y0) / 2;
                  return intSqRootRecursive(x, y1);
              } else {
                  return y0;
              } // end if...else
          } // end intSqRootRecursive
          
          public static int intSqRootIterative(int x) {
              // square roots of 0 and 1 are trivial and
              // y == 0 will cause a divide-by-zero exception
              if (x == 0 || x == 1) {
                  return x;
              } // end if
              int y;
              // starting with y = x / 2 avoids overflow issues
              for (y = x / 2; y > x / y; y = ((x / y) + y) / 2);
              return y;
          } // end intSqRootIterative
          

          您可以测试递归解决方案以了解在帧堆栈上会产生多少实例,但您会发现它收敛得非常快。有趣的是,迭代解决方案比递归解决方案更小更快,这通常不是这种情况,这也是为什么在可以预测堆栈资源足以满足递归深度的情况下使用递归的原因。

          【讨论】:

            【解决方案7】:

            编码:

            def sqrt(n):
              low = 0
              high = n+1
              while high-low > 1:
                mid = (low+high) / 2
                if mid*mid <= n:
                  low = mid
                else:
                  high = mid
              return low
            

            要理解它,只要想到循环不变量,即:

            低高

            如果你理解这段代码,编写一个递归版本应该是微不足道的。

            【讨论】:

            【解决方案8】:

            我假设这是家庭作业,所以我只提供一个提示。

            要进行二分搜索,请选择一个尽可能接近可能正确值中位数的点。所以问题变成了平方根的典型中值是多少,它要么是常数,要么可以通过乘法计算。显然,对于大多数输入而言,使用任意常数将不起作用,因此您需要通过将输入乘以常数来得出您的猜测。

            至于要乘以的常数 C 应该是什么,应该根据您期望作为输入的值来选择。例如,如果您预计您的输入约为 250,000,那么:

            C * 250,000 ~= sqrt(250,000)
            C = sqrt(250,000) / 250,000
            C = 500 / 250,000
            C = 1 / 500
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-09-08
              • 1970-01-01
              • 2011-02-12
              相关资源
              最近更新 更多