Двоичный поиск для вычисления квадратного корня (Java)

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

Это то, что у меня есть до сих пор:

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;
    }
}

тот факт, что он должен использовать двоичный поиск для вычисления квадратного корня, - это часть, которая меня смущает. Если у кого-то есть какие-либо предложения о том, как это сделать, было бы очень признательно. Спасибо

8 ответов


Teh codez:

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

чтобы понять это, просто подумайте об инварианте цикла, а именно:

низкийнизкий высокий

Если вы понимаете этот код, написание рекурсивной версии должно быть тривиальным.


вы можете использовать этот метод 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;
    }
}

вы можете управлять своим собственным рекурсивным методом


по сути идея заключается в том, что вы можете использовать бинарный поиск, чтобы приблизиться к ответу.

например, скажем, вам дается 14 в качестве входных данных. Потом, вы уверены, что квадратный корень из 14 находится между 0 и 14. Итак, 0 и 14-ваши текущие "границы". Вы делите эти две конечные точки и получаете среднюю точку: 7. Затем вы пытаетесь 7 в качестве кандидата - если квадрат 7 больше 14, то у вас есть новая граница (0,7); в противном случае у вас была бы новая граница (7,14).

вы продолжаете повторять это деление, пока не "достаточно близко" к ответу, например, у вас есть квадрат числа, который находится в пределах 14-0.01 и 14+0.01-тогда вы заявляете, что в качестве ответа.

хорошо, этот намек должен быть достаточно хорош для HW. Не забудьте привести StackOverflow.


Я предполагаю, что это домашнее задание, поэтому я только дам подсказку.

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

Что касается того, на что должна умножаться константа C, это должно быть выбрано на основе того, какие значения вы ожидаете в качестве входных. Например, если вы ожидаете, что ваши входные данные будут около 250,000, то:

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

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

подумайте о двоичном поиске как о разделении "пространства" решения пополам, сохраняя половину решения и делая это последовательно, чтобы процесс сходился к решению. Ключевыми понятиями для этого являются: разработайте решение "пространство", которое имеет следующие свойства:

1) можно разделить, обычно пополам или хотя бы на две части

2) из двух частей после подразделения есть способ определить, какая половина имеет решение, так что процесс может быть повторен только на одной половине.

рекурсия включает функцию (метод в O-o speak), вызывающую себя. Рекурсия очень хорошо работает для процесса, который сходится к выводу. Он либо возвращается навсегда или пока у вас не закончится какой-то ресурс, обычно память, и он фатально остановится. Двумя ключевыми понятиями рекурсии являются:

1) сходимость через некоторую инвариантность (подробнее об инвариантности ниже).

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

теперь, для вашей процедуры квадратного корня. Требования к процедуре:

1) число входных данных.

2) целочисленное приближение квадратного корня, которое дает целое число пола ближе всего к фактическому квадратному корню.

3) использовать рекурсию.

4) Используйте двоичный поиск.

это помогает узнать некоторую математику о квадратных корнях для этого. Также полезны понятия элементарного исчисления и аналитической геометрии. Давайте сделаем некоторые рассуждения.

у нас есть произвольное положительное целое число x. Мы хотим его корень y. Если мы выберем некоторое тестовое значение для y, мы увидим, является ли оно корнем x, если y * y = 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 * yn

простой ответ заключается в том, что, когда мы начинаем с y0 > y, первый новый yn, который меньше или равен y, то y - yn

вот основные итерационные и рекурсивные решения. Решения не включают безопасность функции для обеспечения отрицательных значений не вводятся для x. Одна из основных проблем заключается в том, чтобы избежать деления на ноль, если кто-то хочет найти квадратный корень из 0. Поскольку это тривиальный ответ, рекурсивный и итерационный методы возвращают 0 до деления на ноль. Как рекурсивные, так и итеративные решения работают с тривиальными случаями для нахождения квадратных корней 0 и 1.

есть еще один анализ, который всегда нужно делать с int и длинной арифметикой в Ява. Основная проблема-переполнение целого числа, так как Java ничего не делает о int или long overflow. Переполнение приводит к двоичным значениям дополнения (посмотрите, что в другом месте), которые могут привести к фиктивным результатам, и Java не бросает исключения с int или long overflow.

в этом случае легко избежать арифметики, которая может привести к внутреннему переполнению с большими значениями x. Если мы создадим условие завершения, такое как y0 * y0 y. x / 2 работает для всех значений x > 1. Поскольку квадратный корень из X, где x равно 0 или 1 просто x, мы можем легко проверить эти значения и просто вернуть правильное и тривиальное значение. Можно создать код для предотвращения использования значений Integer.МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ. То же самое можно применить, если мы используем long вместо int. Добро пожаловать в computing в реальном мире!

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

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


вот рекурсивное решение в 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);
        }

    }
}

итеративное двоичное решение:

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;
}

решение edst хорошо, но в строке 11 есть ошибка:

mid = (high - low) / 2;

должно быть

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